#include "global.h"
#include "battle.h"
#include "battle_anim.h"
#include "battle_message.h"
#include "main.h"
#include "menu.h"
#include "menu_helpers.h"
#include "scanline_effect.h"
#include "palette.h"
#include "party_menu.h"
#include "pokemon_icon.h"
#include "sprite.h"
#include "item.h"
#include "task.h"
#include "bg.h"
#include "gpu_regs.h"
#include "window.h"
#include "text.h"
#include "text_window.h"
#include "international_string_util.h"
#include "strings.h"
#include "battle_ai_main.h"
#include "battle_ai_util.h"
#include "list_menu.h"
#include "decompress.h"
#include "trainer_pokemon_sprites.h"
#include "malloc.h"
#include "string_util.h"
#include "util.h"
#include "data.h"
#include "reset_rtc_screen.h"
#include "reshow_battle_screen.h"
#include "constants/abilities.h"
#include "constants/party_menu.h"
#include "constants/moves.h"
#include "constants/items.h"
#include "constants/rgb.h"
#include "constants/hold_effects.h"

#define MAX_MODIFY_DIGITS 4

struct BattleDebugModifyArrows
{
    u8 arrowSpriteId[2];
    u16 minValue;
    u16 maxValue;
    int currValue;
    u8 currentDigit:4;
    u8 maxDigits:4;
    u8 charDigits[MAX_MODIFY_DIGITS];
    void *modifiedValPtr;
    u8 typeOfVal;
};

struct BattleDebugMenu
{
    u8 battlerId:2;
    u8 aiBattlerId:2;

    u8 battlerWindowId;

    u8 mainListWindowId;
    u8 mainListTaskId;
    u8 currentMainListItemId;

    u8 secondaryListWindowId;
    u8 secondaryListTaskId;
    u8 currentSecondaryListItemId;
    u8 secondaryListItemCount;

    u8 modifyWindowId;

    u8 activeWindow;

    struct BattleDebugModifyArrows modifyArrows;
    const struct BitfieldInfo *bitfield;
    bool8 battlerWasChanged[MAX_BATTLERS_COUNT];

    u8 aiViewState;

    u8 aiMonSpriteId;
    u8 aiMovesWindowId;

    union
    {
        u8 aiIconSpriteIds[MAX_BATTLERS_COUNT];
        u8 aiPartyIcons[PARTY_SIZE];
    } spriteIds;
};

struct __attribute__((__packed__)) BitfieldInfo
{
    u8 bitsCount;
    u8 currBit;
};

enum
{
    LIST_ITEM_MOVES,
    LIST_ITEM_ABILITY,
    LIST_ITEM_HELD_ITEM,
    LIST_ITEM_PP,
    LIST_ITEM_TYPES,
    LIST_ITEM_STATS,
    LIST_ITEM_STAT_STAGES,
    LIST_ITEM_STATUS1,
    LIST_ITEM_VOLATILE,
    LIST_ITEM_HAZARDS,
    LIST_ITEM_SIDE_STATUS,
    LIST_ITEM_AI,
    LIST_ITEM_AI_MOVES_PTS,
    LIST_ITEM_AI_INFO,
    LIST_ITEM_AI_PARTY,
    LIST_ITEM_VARIOUS,
    LIST_ITEM_INSTANT_WIN,
    LIST_ITEM_COUNT
};

enum
{
    LIST_STAT_HP_CURRENT,
    LIST_STAT_HP_MAX,
    LIST_STAT_ATTACK,
    LIST_STAT_DEFENSE,
    LIST_STAT_SPEED,
    LIST_STAT_SP_ATK,
    LIST_STAT_SP_DEF,
};

enum
{
    LIST_STATUS1_SLEEP,
    LIST_STATUS1_POISON,
    LIST_STATUS1_BURN,
    LIST_STATUS1_FREEZE,
    LIST_STATUS1_PARALYSIS,
    LIST_STATUS1_TOXIC_POISON,
    LIST_STATUS1_TOXIC_COUNTER,
    LIST_STATUS1_FROSTBITE,
};

enum
{
    LIST_SIDE_STICKY_WEB,
    LIST_SIDE_SPIKES,
    LIST_SIDE_TOXIC_SPIKES,
    LIST_SIDE_STEALTH_ROCK,
    LIST_SIDE_STEELSURGE,
};

enum
{
    LIST_SIDE_REFLECT,
    LIST_SIDE_LIGHTSCREEN,
    LIST_SIDE_SAFEGUARD,
    LIST_SIDE_MIST,
    LIST_SIDE_TAILWIND,
    LIST_SIDE_AURORA_VEIL,
    LIST_SIDE_LUCKY_CHANT,
    LIST_SIDE_DAMAGE_NON_TYPES,
    LIST_SIDE_RAINBOW,
    LIST_SIDE_SEA_OF_FIRE,
    LIST_SIDE_SWAMP,
};

enum
{
    LIST_AI_CHECK_BAD_MOVE,
    LIST_AI_TRY_TO_FAINT,
    LIST_AI_CHECK_VIABILITY,
    LIST_AI_FORCE_SETUP_FIRST_TURN,
    LIST_AI_RISKY,
    LIST_AI_TRY_TO_2HKO,
    LIST_AI_PREFER_BATON_PASS,
    LIST_AI_DOUBLE_BATTLE,
    LIST_AI_HP_AWARE,
    LIST_AI_POWERFUL_STATUS,
    LIST_AI_NEGATE_UNAWARE,
    LIST_AI_WILL_SUICIDE,
    LIST_AI_PREFER_STATUS_MOVES,
    LIST_AI_STALL,
    LIST_AI_SMART_SWITCHING,
    LIST_AI_ACE_POKEMON,
    LIST_AI_OMNISCIENT,
    LIST_AI_SMART_MON_CHOICES,
    LIST_AI_CONSERVATIVE,
    LIST_AI_SEQUENCE_SWITCHING,
    LIST_AI_DOUBLE_ACE_POKEMON,
    LIST_AI_WEIGH_ABILITY_PREDICTION,
    LIST_AI_PREFER_HIGHEST_DAMAGE_MOVE,
    LIST_AI_PREDICT_SWITCH,
    LIST_AI_PREDICT_INCOMING_MON,
    LIST_AI_DYNAMIC_FUNC,
    LIST_AI_ROAMING,
    LIST_AI_SAFARI,
    LIST_AI_FIRST_BATTLE,
};

enum
{
    VARIOUS_SHOW_HP,
    VARIOUS_SUBSTITUTE_HP,
    VARIOUS_IN_LOVE,
};

enum
{
    ACTIVE_WIN_MAIN,
    ACTIVE_WIN_SECONDARY,
    ACTIVE_WIN_MODIFY
};

enum
{
    VAL_U8,
    VAL_U16,
    VAL_U32,
    VAL_BITFIELD_8,
    VAL_BITFIELD_16,
    VAL_BITFIELD_32,
    VAL_VOLATILE,
    VAL_HAZARDS,
    VAR_SIDE_STATUS,
    VAR_SHOW_HP,
    VAR_SUBSTITUTE,
    VAR_IN_LOVE,
    VAR_U16_4_ENTRIES,
    VAL_S8,
    VAL_ALL_STAT_STAGES,
};

// Static Declarations
static const u8 *GetHoldEffectName(enum ItemHoldEffect holdEffect);

// const rom data
static const u8 sText_Ability[] = _("Ability");
static const u8 sText_HeldItem[] = _("Held Item");
static const u8 sText_HoldEffect[] = _("Hold Effect");
static const u8 sText_EmptyString[] = _("");

static const struct BitfieldInfo sStatus1Bitfield[] =
{
    {/*Sleep*/ 3, 0},
    {/*Poison*/ 1, 3},
    {/*Burn*/ 1, 4},
    {/*Freeze*/ 1, 5},
    {/*Paralysis*/1, 6},
    {/*Toxic Poison*/ 1, 7},
    {/*Toxic Counter*/ 4, 8},
    {/*Frostbite*/ 1, 12},
};

static const struct BitfieldInfo sStatus3Bitfield[] =
{
    {/*Leech Seed Battler*/ 2, 0},
    {/*Leech Seed*/ 1, 2},
    {/*Always Hits*/ 2, 3},
    {/*Perish Song*/ 1, 5},
    {/*On Air*/ 1, 6},
    {/*Underground*/ 1, 7},
    {/*Minimized*/ 1, 8},
    {/*Charged Up*/ 1, 9},
    {/*Rooted*/ 1, 10},
    {/*Yawn*/ 2, 11},
    {/*Imprisoned Others*/ 1, 13},
    {/*Grudge*/ 1, 14},
    {/*Gastro Acid*/ 1, 16},
    {/*Embargo*/ 1, 17},
    {/*Underwater*/ 1, 18},
    {/*Smacked Down*/ 1, 21},
    {/*Telekinesis*/ 1, 23},
    {/*Miracle Eyed*/ 1, 25},
    {/*Magnet Rise*/ 1, 26},
    {/*Heal Blocked*/ 1, 27},
    {/*Aqua Ring*/ 1, 28},
    {/*Laser Focus*/ 1, 29},
    {/*Power Trick*/ 1, 30},
};

static const struct BitfieldInfo sAIBitfield[] =
{
    {/*Check Bad Move*/ 1, 0},
    {/*Try to Faint*/ 1, 1},
    {/*Check Viability*/ 1, 2},
    {/*Force Setup First Turn*/ 1, 3},
    {/*Risky*/ 1, 4},
    {/*Prefer Strongest Move*/ 1, 5},
    {/*Prefer Baton Pass*/ 1, 6},
    {/*Double Battle*/ 1, 7},
    {/*HP Aware*/ 1, 8},
    {/*Powerful Status*/ 1, 9},
    {/*Negate Unaware*/ 1, 10},
    {/*Will Suicide*/ 1, 11},
    {/*Prefer Status Moves*/ 1, 12},
    {/*Stall*/ 1, 13},
    {/*Smart Switching*/ 1, 14},
    {/*Ace Pokemon*/ 1, 15},
    {/*Omniscient*/ 1, 16},
    {/*Smart Mon Choices*/ 1, 17},
    {/*Conservative*/ 1, 18},
    {/*Sequence Switching*/ 1, 19},
    {/*Double Ace Pokemon*/ 1, 20},
    {/*Weigh Ability Prediction*/ 1, 21},
    {/*Prefer Highest Damage Move*/ 1, 22},
    {/*Predict Switch*/ 1, 23},
    {/*Predict Incoming Mon*/ 1, 24},
    {/*Dynamic Func*/ 1, 28},
    {/*Roaming*/ 1, 29},
    {/*Safari*/ 1, 30},
    {/*First Battle*/ 1, 31},
};

static const struct ListMenuItem sMainListItems[] =
{
    {COMPOUND_STRING("Moves"),        LIST_ITEM_MOVES},
    {sText_Ability,                   LIST_ITEM_ABILITY},
    {sText_HeldItem,                  LIST_ITEM_HELD_ITEM},
    {COMPOUND_STRING("PP"),           LIST_ITEM_PP},
    {COMPOUND_STRING("Types"),        LIST_ITEM_TYPES},
    {COMPOUND_STRING("Stats"),        LIST_ITEM_STATS},
    {COMPOUND_STRING("Stat Stages"),  LIST_ITEM_STAT_STAGES},
    {COMPOUND_STRING("Status1"),      LIST_ITEM_STATUS1},
    {COMPOUND_STRING("Volatiles"),    LIST_ITEM_VOLATILE},
    {COMPOUND_STRING("Hazards"),      LIST_ITEM_HAZARDS},
    {COMPOUND_STRING("Side Status"),  LIST_ITEM_SIDE_STATUS},
    {COMPOUND_STRING("AI"),           LIST_ITEM_AI},
    {COMPOUND_STRING("AI Pts/Dmg"),   LIST_ITEM_AI_MOVES_PTS},
    {COMPOUND_STRING("AI Info"),      LIST_ITEM_AI_INFO},
    {COMPOUND_STRING("AI Party"),     LIST_ITEM_AI_PARTY},
    {COMPOUND_STRING("Various"),      LIST_ITEM_VARIOUS},
    {COMPOUND_STRING("Instant Win"),  LIST_ITEM_INSTANT_WIN},
};

static const struct ListMenuItem sStatsListItems[] =
{
    {COMPOUND_STRING("HP Current"), LIST_STAT_HP_CURRENT},
    {COMPOUND_STRING("HP Max"),     LIST_STAT_HP_MAX},
    {COMPOUND_STRING("Attack"),     LIST_STAT_ATTACK},
    {COMPOUND_STRING("Defense"),    LIST_STAT_DEFENSE},
    {COMPOUND_STRING("Speed"),      LIST_STAT_SPEED},
    {COMPOUND_STRING("Sp. Atk"),    LIST_STAT_SP_ATK},
    {COMPOUND_STRING("Sp. Def"),    LIST_STAT_SP_DEF},
};

static const struct ListMenuItem sStatus1ListItems[] =
{
    {COMPOUND_STRING("Sleep"),         LIST_STATUS1_SLEEP},
    {COMPOUND_STRING("Poison"),        LIST_STATUS1_POISON},
    {COMPOUND_STRING("Burn"),          LIST_STATUS1_BURN},
    {COMPOUND_STRING("Freeze"),        LIST_STATUS1_FREEZE},
    {COMPOUND_STRING("Paralysis"),     LIST_STATUS1_PARALYSIS},
    {COMPOUND_STRING("Toxic Poison"),  LIST_STATUS1_TOXIC_POISON},
    {COMPOUND_STRING("Toxic Counter"), LIST_STATUS1_TOXIC_COUNTER},
    {COMPOUND_STRING("Frostbite"),     LIST_STATUS1_FROSTBITE},
};

static const struct ListMenuItem sVolatileStatusListItems[] =
{
    {COMPOUND_STRING("Confusion"),          VOLATILE_CONFUSION},
    {COMPOUND_STRING("Flinched"),           VOLATILE_FLINCHED},
    {COMPOUND_STRING("Torment"),            VOLATILE_TORMENT},
    {COMPOUND_STRING("Powder"),             VOLATILE_POWDER},
    {COMPOUND_STRING("DefenseCurl"),        VOLATILE_DEFENSE_CURL},
    {COMPOUND_STRING("Recharge"),           VOLATILE_RECHARGE},
    {COMPOUND_STRING("Rage"),               VOLATILE_RAGE},
    {COMPOUND_STRING("DestinyBond"),        VOLATILE_DESTINY_BOND},
    {COMPOUND_STRING("EscapePrevention"),   VOLATILE_ESCAPE_PREVENTION},
    {COMPOUND_STRING("Cursed"),             VOLATILE_CURSED},
    {COMPOUND_STRING("Foresight"),          VOLATILE_FORESIGHT},
    {COMPOUND_STRING("DragonCheer"),        VOLATILE_DRAGON_CHEER},
    {COMPOUND_STRING("FocusEnergy"),        VOLATILE_FOCUS_ENERGY},
    {COMPOUND_STRING("Electrified"),        VOLATILE_ELECTRIFIED},
    {COMPOUND_STRING("MudSport"),           VOLATILE_MUD_SPORT},
    {COMPOUND_STRING("WaterSport"),         VOLATILE_WATER_SPORT},
    {COMPOUND_STRING("Infinite Confusion"), VOLATILE_INFINITE_CONFUSION},
    {COMPOUND_STRING("Salt Cure"),          VOLATILE_SALT_CURE},
    {COMPOUND_STRING("Syrup Bomb"),         VOLATILE_SYRUP_BOMB},
    {COMPOUND_STRING("Glaive Rush"),        VOLATILE_GLAIVE_RUSH},
    {COMPOUND_STRING("Leech Seed"),         VOLATILE_LEECH_SEED},
    {COMPOUND_STRING("Lock On"),            VOLATILE_LOCK_ON},
    {COMPOUND_STRING("Perish Song"),        VOLATILE_PERISH_SONG},
    {COMPOUND_STRING("Minimize"),           VOLATILE_MINIMIZE},
    {COMPOUND_STRING("Charge"),             VOLATILE_CHARGE},
    {COMPOUND_STRING("Root"),               VOLATILE_ROOT},
    {COMPOUND_STRING("Yawn"),               VOLATILE_YAWN},
    {COMPOUND_STRING("Imprison"),           VOLATILE_IMPRISON},
    {COMPOUND_STRING("Grudge"),             VOLATILE_GRUDGE},
    {COMPOUND_STRING("Gastro Acid"),        VOLATILE_GASTRO_ACID},
    {COMPOUND_STRING("Embargo"),            VOLATILE_EMBARGO},
    {COMPOUND_STRING("Smack Down"),         VOLATILE_SMACK_DOWN},
    {COMPOUND_STRING("Telekinesis"),        VOLATILE_TELEKINESIS},
    {COMPOUND_STRING("Miracle Eye"),        VOLATILE_MIRACLE_EYE},
    {COMPOUND_STRING("Magnet Rise"),        VOLATILE_MAGNET_RISE},
    {COMPOUND_STRING("Heal Block"),         VOLATILE_HEAL_BLOCK},
    {COMPOUND_STRING("Aqua Ring"),          VOLATILE_AQUA_RING},
    {COMPOUND_STRING("Laser Focus"),        VOLATILE_LASER_FOCUS},
    {COMPOUND_STRING("Power Trick"),        VOLATILE_POWER_TRICK},
};

static const struct ListMenuItem sHazardsListItems[] =
{
    {COMPOUND_STRING("Spikes"),       LIST_SIDE_SPIKES},
    {COMPOUND_STRING("Sticky Web"),   LIST_SIDE_STICKY_WEB},
    {COMPOUND_STRING("Toxic Spikes"), LIST_SIDE_TOXIC_SPIKES},
    {COMPOUND_STRING("Stealth Rock"), LIST_SIDE_STEALTH_ROCK},
    {COMPOUND_STRING("Steelsurge"),   LIST_SIDE_STEELSURGE},
};

static const struct ListMenuItem sSideStatusListItems[] =
{
    {COMPOUND_STRING("Reflect"),          LIST_SIDE_REFLECT},
    {COMPOUND_STRING("Light Screen"),     LIST_SIDE_LIGHTSCREEN},
    {COMPOUND_STRING("Safeguard"),        LIST_SIDE_SAFEGUARD},
    {COMPOUND_STRING("Mist"),             LIST_SIDE_MIST},
    {COMPOUND_STRING("Tailwind"),         LIST_SIDE_TAILWIND},
    {COMPOUND_STRING("Aurora Veil"),      LIST_SIDE_AURORA_VEIL},
    {COMPOUND_STRING("Lucky Chant"),      LIST_SIDE_LUCKY_CHANT},
    {COMPOUND_STRING("Damage Non-Types"), LIST_SIDE_DAMAGE_NON_TYPES},
    {COMPOUND_STRING("Rainbow"),          LIST_SIDE_RAINBOW},
    {COMPOUND_STRING("Sea of Fire"),      LIST_SIDE_SEA_OF_FIRE},
    {COMPOUND_STRING("Swamp"),            LIST_SIDE_SWAMP},
};

static const struct ListMenuItem sAIListItems[] =
{
    {COMPOUND_STRING("Check Bad Move"),             LIST_AI_CHECK_BAD_MOVE},
    {COMPOUND_STRING("Try to Faint"),               LIST_AI_TRY_TO_FAINT},
    {COMPOUND_STRING("Check Viability"),            LIST_AI_CHECK_VIABILITY},
    {COMPOUND_STRING("Force Setup First Turn"),     LIST_AI_FORCE_SETUP_FIRST_TURN},
    {COMPOUND_STRING("Risky"),                      LIST_AI_RISKY},
    {COMPOUND_STRING("Try to 2HKO"),                LIST_AI_TRY_TO_2HKO},
    {COMPOUND_STRING("Prefer Baton Pass"),          LIST_AI_PREFER_BATON_PASS},
    {COMPOUND_STRING("Double Battle"),              LIST_AI_DOUBLE_BATTLE},
    {COMPOUND_STRING("HP Aware"),                   LIST_AI_HP_AWARE},
    {COMPOUND_STRING("Powerful Status"),            LIST_AI_POWERFUL_STATUS},
    {COMPOUND_STRING("Negate Unaware"),             LIST_AI_NEGATE_UNAWARE},
    {COMPOUND_STRING("Will Suicide"),               LIST_AI_WILL_SUICIDE},
    {COMPOUND_STRING("Prefer Status Moves"),        LIST_AI_PREFER_STATUS_MOVES},
    {COMPOUND_STRING("Stall"),                      LIST_AI_STALL},
    {COMPOUND_STRING("Smart Switching"),            LIST_AI_SMART_SWITCHING},
    {COMPOUND_STRING("Ace Pokémon"),                LIST_AI_ACE_POKEMON},
    {COMPOUND_STRING("Omniscient"),                 LIST_AI_OMNISCIENT},
    {COMPOUND_STRING("Smart Mon Choices"),          LIST_AI_SMART_MON_CHOICES},
    {COMPOUND_STRING("Conservative"),               LIST_AI_CONSERVATIVE},
    {COMPOUND_STRING("Sequence Switching"),         LIST_AI_SEQUENCE_SWITCHING},
    {COMPOUND_STRING("Double Ace Pokémon"),         LIST_AI_DOUBLE_ACE_POKEMON},
    {COMPOUND_STRING("Weigh Ability Prediction"),   LIST_AI_WEIGH_ABILITY_PREDICTION},
    {COMPOUND_STRING("Prefer Highest Damage Move"), LIST_AI_PREFER_HIGHEST_DAMAGE_MOVE},
    {COMPOUND_STRING("Predict Switch"),             LIST_AI_PREDICT_SWITCH},
    {COMPOUND_STRING("Predict Incoming Mon"),       LIST_AI_PREDICT_INCOMING_MON},
    {COMPOUND_STRING("Dynamic Func"),               LIST_AI_DYNAMIC_FUNC},
    {COMPOUND_STRING("Roaming"),                    LIST_AI_ROAMING},
    {COMPOUND_STRING("Safari"),                     LIST_AI_SAFARI},
    {COMPOUND_STRING("First Battle"),               LIST_AI_FIRST_BATTLE},
};

static const struct ListMenuItem sVariousListItems[] =
{
    {COMPOUND_STRING("Show HP"),       VARIOUS_SHOW_HP},
    {COMPOUND_STRING("Substitute HP"), VARIOUS_SUBSTITUTE_HP},
    {COMPOUND_STRING("In Love"),       VARIOUS_IN_LOVE},
};

static const struct ListMenuItem sSecondaryListItems[] =
{
    {sText_EmptyString, 0},
    {sText_EmptyString, 1},
    {sText_EmptyString, 2},
    {sText_EmptyString, 3},
    {sText_EmptyString, 4},
    {sText_EmptyString, 5},
    {sText_EmptyString, 6},
    {sText_EmptyString, 7},
    {sText_EmptyString, 8},
};


static const struct ListMenuTemplate sMainListTemplate =
{
    .items = sMainListItems,
    .moveCursorFunc = NULL,
    .itemPrintFunc = NULL,
    .totalItems = ARRAY_COUNT(sMainListItems),
    .maxShowed = 6,
    .windowId = 0,
    .header_X = 0,
    .item_X = 8,
    .cursor_X = 0,
    .upText_Y = 1,
    .cursorPal = 2,
    .fillValue = 1,
    .cursorShadowPal = 3,
    .lettersSpacing = 1,
    .itemVerticalPadding = 0,
    .scrollMultiple = LIST_NO_MULTIPLE_SCROLL,
    .fontId = 1,
    .cursorKind = 0
};

static const struct ListMenuTemplate sSecondaryListTemplate =
{
    .items = sSecondaryListItems,
    .moveCursorFunc = NULL,
    .itemPrintFunc = NULL,
    .totalItems = 0,
    .maxShowed = 0,
    .windowId = 0,
    .header_X = 0,
    .item_X = 8,
    .cursor_X = 0,
    .upText_Y = 1,
    .cursorPal = 2,
    .fillValue = 1,
    .cursorShadowPal = 3,
    .lettersSpacing = 1,
    .itemVerticalPadding = 0,
    .scrollMultiple = LIST_NO_MULTIPLE_SCROLL,
    .fontId = 1,
    .cursorKind = 0
};


static const struct WindowTemplate sMainListWindowTemplate =
{
    .bg = 0,
    .tilemapLeft = 1,
    .tilemapTop = 3,
    .width = 9,
    .height = 12,
    .paletteNum = 0xF,
    .baseBlock = 0x1
};

static const struct WindowTemplate sSecondaryListWindowTemplate =
{
    .bg = 0,
    .tilemapLeft = 12,
    .tilemapTop = 3,
    .width = 20,
    .height = 16,
    .paletteNum = 0xF,
    .baseBlock = 0x6D
};

static const struct WindowTemplate sModifyWindowTemplate =
{
    .bg = 0,
    .tilemapLeft = 25,
    .tilemapTop = 2,
    .width = 4,
    .height = 2,
    .paletteNum = 0xF,
    .baseBlock = 0x1AD
};

static const struct WindowTemplate sBattlerWindowTemplate =
{
    .bg = 0,
    .tilemapLeft = 10,
    .tilemapTop = 0,
    .width = 14,
    .height = 2,
    .paletteNum = 0xF,
    .baseBlock = 0x1B5
};

static const struct BgTemplate sBgTemplates[] =
{
   {
       .bg = 0,
       .charBaseIndex = 0,
       .mapBaseIndex = 31,
       .screenSize = 0,
       .paletteMode = 0,
       .priority = 1,
       .baseTile = 0
   },
   {
       .bg = 1,
       .charBaseIndex = 2,
       .mapBaseIndex = 20,
       .screenSize = 0,
       .paletteMode = 0,
       .priority = 0,
       .baseTile = 0
   }
};

static const bool8 sHasChangeableEntries[LIST_ITEM_COUNT] =
{
    [LIST_ITEM_MOVES] = TRUE,
    [LIST_ITEM_AI_MOVES_PTS] = TRUE,
    [LIST_ITEM_PP] = TRUE,
    [LIST_ITEM_ABILITY] = TRUE,
    [LIST_ITEM_TYPES] = TRUE,
    [LIST_ITEM_HELD_ITEM] = TRUE,
    [LIST_ITEM_STAT_STAGES] = TRUE,
};

static const u16 sBgColor[] = {RGB_WHITE};

// this file's functions
static void Task_DebugMenuFadeOut(u8 taskId);
static void Task_DebugMenuProcessInput(u8 taskId);
static void Task_DebugMenuFadeIn(u8 taskId);
static void PrintOnBattlerWindow(u8 windowId, u8 battlerId);
static void UpdateWindowsOnChangedBattler(struct BattleDebugMenu *data);
static void CreateSecondaryListMenu(struct BattleDebugMenu *data);
static void PrintSecondaryEntries(struct BattleDebugMenu *data);
static void DestroyModifyArrows(struct BattleDebugMenu *data);
static void PrintDigitChars(struct BattleDebugMenu *data);
static void SetUpModifyArrows(struct BattleDebugMenu *data);
static void UpdateBattlerValue(struct BattleDebugMenu *data);
static void UpdateMonData(struct BattleDebugMenu *data);
static void ChangeHazardsValue(struct BattleDebugMenu *data);
static u32 GetHazardsValue(struct BattleDebugMenu *data);
static u16 *GetSideStatusValue(struct BattleDebugMenu *data, bool32 changeStatus, bool32 statusTrue);
static bool32 TryMoveDigit(struct BattleDebugModifyArrows *modArrows, bool32 moveUp);
static void SwitchToDebugView(u8 taskId);
static void SwitchToDebugViewFromAiParty(u8 taskId);

// code
static struct BattleDebugMenu *GetStructPtr(u8 taskId)
{
    u8 *taskDataPtr = (u8 *)(&gTasks[taskId].data[0]);

    return (struct BattleDebugMenu*)(T1_READ_PTR(taskDataPtr));
}

static void SetStructPtr(u8 taskId, void *ptr)
{
    u32 structPtr = (u32)(ptr);
    u8 *taskDataPtr = (u8 *)(&gTasks[taskId].data[0]);

    taskDataPtr[0] = structPtr >> 0;
    taskDataPtr[1] = structPtr >> 8;
    taskDataPtr[2] = structPtr >> 16;
    taskDataPtr[3] = structPtr >> 24;
}

static void MainCB2(void)
{
    RunTasks();
    AnimateSprites();
    BuildOamBuffer();
    UpdatePaletteFade();
}

static void VBlankCB(void)
{
    LoadOam();
    ProcessSpriteCopyRequests();
    TransferPlttBuffer();
}

void CB2_BattleDebugMenu(void)
{
    u8 taskId;
    struct BattleDebugMenu *data;

    switch (gMain.state)
    {
    default:
    case 0:
        SetVBlankCallback(NULL);
        gMain.state++;
        break;
    case 1:
        ResetVramOamAndBgCntRegs();
        SetGpuReg(REG_OFFSET_DISPCNT, 0);
        ResetBgsAndClearDma3BusyFlags(0);
        InitBgsFromTemplates(0, sBgTemplates, ARRAY_COUNT(sBgTemplates));
        ResetAllBgsCoordinates();
        FreeAllWindowBuffers();
        DeactivateAllTextPrinters();
        SetGpuReg(REG_OFFSET_DISPCNT, DISPCNT_OBJ_ON | DISPCNT_OBJ_1D_MAP);
        ShowBg(0);
        ShowBg(1);
        gMain.state++;
        break;
    case 2:
        ResetPaletteFade();
        ScanlineEffect_Stop();
        ResetTasks();
        ResetSpriteData();
        gMain.state++;
        break;
    case 3:
        LoadPalette(sBgColor, 0, 2);
        LoadPalette(GetOverworldTextboxPalettePtr(), 0xf0, 16);
        gMain.state++;
        break;
    case 4:
        taskId = CreateTask(Task_DebugMenuFadeIn, 0);
        data = AllocZeroed(sizeof(struct BattleDebugMenu));
        SetStructPtr(taskId, data);

        data->battlerId = gBattleStruct->debugBattler;
        data->battlerWindowId = AddWindow(&sBattlerWindowTemplate);
        PutWindowTilemap(data->battlerWindowId);
        PrintOnBattlerWindow(data->battlerWindowId, data->battlerId);

        data->mainListWindowId = AddWindow(&sMainListWindowTemplate);

        gMultiuseListMenuTemplate = sMainListTemplate;
        gMultiuseListMenuTemplate.windowId = data->mainListWindowId;
        data->mainListTaskId = ListMenuInit(&gMultiuseListMenuTemplate, 0, 0);

        data->currentMainListItemId = 0;
        data->activeWindow = ACTIVE_WIN_MAIN;
        data->secondaryListTaskId = 0xFF;
        CopyWindowToVram(data->mainListWindowId, COPYWIN_FULL);
        gMain.state++;
        break;
    case 5:
        BeginNormalPaletteFade(-1, 0, 0x10, 0, 0);
        SetVBlankCallback(VBlankCB);
        SetMainCallback2(MainCB2);
        return;
    }
}

static void PutMovesPointsText(struct BattleDebugMenu *data)
{
    u32 i, j, count, battlerDef;
    u8 *text = Alloc(0x50);

    FillWindowPixelBuffer(data->aiMovesWindowId, 0x11);
    for (i = 0; i < MAX_MON_MOVES; i++)
    {
        text[0] = CHAR_SPACE;
        StringCopy(text + 1, GetMoveName(gBattleMons[data->aiBattlerId].moves[i]));
        AddTextPrinterParameterized(data->aiMovesWindowId, FONT_NORMAL, text, 0, i * 15, 0, NULL);
        for (count = 0, j = 0; j < MAX_BATTLERS_COUNT; j++)
        {
            if (data->spriteIds.aiIconSpriteIds[j] == 0xFF)
                continue;
            battlerDef = gSprites[data->spriteIds.aiIconSpriteIds[j]].data[0];
            ConvertIntToDecimalStringN(text,
                                       gAiBattleData->finalScore[data->aiBattlerId][battlerDef][i],
                                       STR_CONV_MODE_RIGHT_ALIGN, 3);
            AddTextPrinterParameterized(data->aiMovesWindowId, FONT_NORMAL, text, 83 + count * 54, i * 15, 0, NULL);

            ConvertIntToDecimalStringN(text,
                                       AI_GetDamage(data->aiBattlerId, battlerDef, i, AI_ATTACKING, gAiLogicData),
                                       STR_CONV_MODE_RIGHT_ALIGN, 3);
            AddTextPrinterParameterized(data->aiMovesWindowId, FONT_NORMAL, text, 110 + count * 54, i * 15, 0, NULL);

            count++;
        }
    }

    if (gAiLogicData->shouldSwitch & (1u << data->aiBattlerId))
    {
        u32 switchMon = GetMonData(&gEnemyParty[gAiLogicData->mostSuitableMonId[data->aiBattlerId]], MON_DATA_SPECIES);
        AddTextPrinterParameterized(data->aiMovesWindowId, FONT_NORMAL, COMPOUND_STRING("Switching to "), 74, 64, 0, NULL);
        AddTextPrinterParameterized(data->aiMovesWindowId, FONT_NORMAL, gSpeciesInfo[switchMon].speciesName, 74 + 68, 64, 0, NULL);
    }
    else
    {
        u32 chosenMoveIndex = gAiBattleData->chosenMoveIndex[data->aiBattlerId];
        AddTextPrinterParameterized(data->aiMovesWindowId, FONT_NORMAL, COMPOUND_STRING("Chosen move: "), 74, 64, 0, NULL);
        AddTextPrinterParameterized(data->aiMovesWindowId, FONT_NORMAL, GetMoveName(gBattleMons[data->aiBattlerId].moves[chosenMoveIndex]), 74 + 68, 64, 0, NULL);
    }

    CopyWindowToVram(data->aiMovesWindowId, COPYWIN_FULL);
    Free(text);
}

static void CleanUpAiInfoWindow(u8 taskId)
{
    u32 i;
    struct BattleDebugMenu *data = GetStructPtr(taskId);

    FreeMonIconPalettes();
    for (i = 0; i < MAX_BATTLERS_COUNT; i++)
    {
        if (data->spriteIds.aiIconSpriteIds[i] != 0xFF)
            FreeAndDestroyMonIconSprite(&gSprites[data->spriteIds.aiIconSpriteIds[i]]);
    }
    FreeAndDestroyMonPicSprite(data->aiMonSpriteId);
    ClearWindowTilemap(data->aiMovesWindowId);
    RemoveWindow(data->aiMovesWindowId);
}

static void Task_ShowAiPoints(u8 taskId)
{
    u32 i, count;
    struct WindowTemplate winTemplate;
    struct BattleDebugMenu *data = GetStructPtr(taskId);
    struct Pokemon *mon;

    switch (data->aiViewState)
    {
    case 0:
        HideBg(0);
        ShowBg(1);

        // Swap battler if it's player mon
        data->aiBattlerId = data->battlerId;
        while (!BattlerHasAi(data->aiBattlerId))
        {
            if (++data->aiBattlerId >= gBattlersCount)
                data->aiBattlerId = 0;
        }
        data->battlerId = data->aiBattlerId;

        LoadMonIconPalettes();
        for (count = 0, i = 0; i < MAX_BATTLERS_COUNT; i++)
        {
            if (i != data->aiBattlerId && IsBattlerAlive(i))
            {
                data->spriteIds.aiIconSpriteIds[i] = CreateMonIcon(gBattleMons[i].species,
                                                         SpriteCallbackDummy,
                                                         95 + (count * 60), 17, 0, 0);
                gSprites[data->spriteIds.aiIconSpriteIds[i]].data[0] = i; // battler id
                count++;
            }
            else
            {
                data->spriteIds.aiIconSpriteIds[i] = 0xFF;
            }
        }
        mon = GetBattlerMon(data->aiBattlerId);

        data->aiMonSpriteId = CreateMonPicSprite(gBattleMons[data->aiBattlerId].species,
                                                 GetMonData(mon, MON_DATA_IS_SHINY),
                                                 gBattleMons[data->aiBattlerId].personality,
                                                 TRUE,
                                                 39, 130, 15, TAG_NONE);
        data->aiViewState++;
        break;
    // Put text
    case 1:
        winTemplate = CreateWindowTemplate(1, 0, 4, 30, 14, 15, 0x200);
        data->aiMovesWindowId = AddWindow(&winTemplate);
        PutWindowTilemap(data->aiMovesWindowId);
        PutMovesPointsText(data);

        data->aiViewState++;
        break;
    // Input
    case 2:
        if (JOY_NEW(R_BUTTON) && IsDoubleBattle())
        {
            CleanUpAiInfoWindow(taskId);
            do {
                data->battlerId++;
                data->battlerId %= gBattlersCount;
            } while (!IsBattlerAlive(data->battlerId));
            data->aiViewState = 0;
        }
        else if (JOY_NEW(L_BUTTON) && IsDoubleBattle())
        {
            CleanUpAiInfoWindow(taskId);
            do {
                if (data->battlerId == 0)
                    data->battlerId = gBattlersCount - 1;
                else
                    data->battlerId--;
            } while (!IsBattlerAlive(data->battlerId) || !BattlerHasAi(data->battlerId));
            data->aiViewState = 0;
        }
        else if (JOY_NEW(SELECT_BUTTON | B_BUTTON))
        {
            SwitchToDebugView(taskId);
            HideBg(1);
            ShowBg(0);
            return;
        }
        break;
    }
}

static void SwitchToAiPointsView(u8 taskId)
{
    gTasks[taskId].func = Task_ShowAiPoints;
    GetStructPtr(taskId)->aiViewState = 0;
}

static const u8 *const sAiInfoItemNames[] =
{
    sText_Ability,
    sText_HeldItem,
    sText_HoldEffect,
};

static void PutAiInfoText(struct BattleDebugMenu *data)
{
    u32 i;
    u8 *text = Alloc(0x50);

    FillWindowPixelBuffer(data->aiMovesWindowId, 0x11);

    // item names
    for (i = 0; i < ARRAY_COUNT(sAiInfoItemNames); i++)
    {
        AddTextPrinterParameterized(data->aiMovesWindowId, FONT_NORMAL, sAiInfoItemNames[i], 3, i * 15, 0, NULL);
    }

    // items info
    for (i = 0; i < gBattlersCount; i++)
    {
        if (IsOnPlayerSide(i) && IsBattlerAlive(i))
        {
            u16 ability = gAiLogicData->abilities[i];
            enum ItemHoldEffect holdEffect = gAiLogicData->holdEffects[i];
            u16 item = gAiLogicData->items[i];
            u8 x = (i == B_POSITION_PLAYER_LEFT) ? 83 + (i) * 75 : 83 + (i-1) * 75;
            AddTextPrinterParameterized(data->aiMovesWindowId, FONT_SMALL, gAbilitiesInfo[ability].name, x, 0, 0, NULL);
            AddTextPrinterParameterized(data->aiMovesWindowId, FONT_SMALL, GetItemName(item), x, 15, 0, NULL);
            AddTextPrinterParameterized(data->aiMovesWindowId, FONT_SMALL, GetHoldEffectName(holdEffect), x, 30, 0, NULL);
        }
    }

    CopyWindowToVram(data->aiMovesWindowId, COPYWIN_FULL);
    Free(text);
}

static void PutAiPartyText(struct BattleDebugMenu *data)
{
    u32 i, j, count;
    u8 *text = Alloc(0x50), *txtPtr;
    struct AiPartyMon *aiMons = gAiPartyData->mons[GetBattlerSide(data->aiBattlerId)];

    FillWindowPixelBuffer(data->aiMovesWindowId, 0x11);
    count = gAiPartyData->count[GetBattlerSide(data->aiBattlerId)];
    for (i = 0; i < count; i++)
    {
        if (aiMons[i].wasSentInBattle)
        {
            text[0] = CHAR_LV;
            txtPtr = ConvertIntToDecimalStringN(text + 1, aiMons[i].level, STR_CONV_MODE_LEFT_ALIGN, 3);
            *txtPtr++ = CHAR_SPACE;
            if (aiMons[i].gender == MON_MALE)
                *txtPtr++ = CHAR_MALE;
            else if (aiMons[i].gender == MON_FEMALE)
                *txtPtr++ = CHAR_FEMALE;
            *txtPtr = EOS;
            AddTextPrinterParameterized5(data->aiMovesWindowId, FONT_SMALL_NARROW, text, i * 41, 0, 0, NULL, 0, 0);
        }

        txtPtr = StringCopyN(text, gAbilitiesInfo[aiMons[i].ability].name, 7); // The screen is too small to fit the whole string, so we need to drop the last letters.
        *txtPtr = EOS;
        AddTextPrinterParameterized5(data->aiMovesWindowId, FONT_SMALL_NARROW, text, i * 41, 15, 0, NULL, 0, 0);

        for (j = 0; j < MAX_MON_MOVES; j++)
        {
            txtPtr = StringCopyN(text, GetMoveName(aiMons[i].moves[j]), 8);
            *txtPtr = EOS;
            AddTextPrinterParameterized5(data->aiMovesWindowId, FONT_SMALL_NARROW, text, i * 41, 35 + j * 15, 0, NULL, 0, 0);
        }

        txtPtr = StringCopyN(text, GetHoldEffectName(aiMons[i].heldEffect), 7);
        *txtPtr = EOS;
        AddTextPrinterParameterized5(data->aiMovesWindowId, FONT_SMALL_NARROW, text, i * 41, 35 + j * 15, 0, NULL, 0, 0);

        txtPtr = ConvertIntToDecimalStringN(text, aiMons[i].switchInCount, STR_CONV_MODE_LEFT_ALIGN, 2);
        *txtPtr = EOS;
        AddTextPrinterParameterized5(data->aiMovesWindowId, FONT_SMALL_NARROW, text, i * 41, 35 + (j + 1) * 15, 0, NULL, 0, 0);
    }

    CopyWindowToVram(data->aiMovesWindowId, COPYWIN_FULL);
    Free(text);
}

static void Task_ShowAiKnowledge(u8 taskId)
{
    u32 i, count;
    struct WindowTemplate winTemplate;
    struct BattleDebugMenu *data = GetStructPtr(taskId);
    struct Pokemon *mon;

    switch (data->aiViewState)
    {
    case 0:
        HideBg(0);
        ShowBg(1);

        // Swap battler if it's player mon
        data->aiBattlerId = data->battlerId;
        while (!BattlerHasAi(data->aiBattlerId))
        {
            if (++data->aiBattlerId >= gBattlersCount)
                data->aiBattlerId = 0;
        }

        LoadMonIconPalettes();
        for (count = 0, i = 0; i < MAX_BATTLERS_COUNT; i++)
        {
            if (IsOnPlayerSide(i) && IsBattlerAlive(i))
            {
                data->spriteIds.aiIconSpriteIds[i] = CreateMonIcon(gBattleMons[i].species,
                                                         SpriteCallbackDummy,
                                                         95 + (count * 80), 17, 0, 0);
                gSprites[data->spriteIds.aiIconSpriteIds[i]].data[0] = i; // battler id
                count++;
            }
            else
            {
                data->spriteIds.aiIconSpriteIds[i] = 0xFF;
            }
        }

        mon = GetBattlerMon(data->aiBattlerId);

        data->aiMonSpriteId = CreateMonPicSprite(gBattleMons[data->aiBattlerId].species,
                                                 GetMonData(mon, MON_DATA_IS_SHINY),
                                                 gBattleMons[data->aiBattlerId].personality,
                                                 TRUE,
                                                 39, 130, 15, TAG_NONE);
        data->aiViewState++;
        break;
    // Put text
    case 1:
        winTemplate = CreateWindowTemplate(1, 0, 4, 27, 14, 15, 0x200);
        data->aiMovesWindowId = AddWindow(&winTemplate);
        PutWindowTilemap(data->aiMovesWindowId);
        PutAiInfoText(data);
        data->aiViewState++;
        break;
    // Input
    case 2:
        if (JOY_NEW(SELECT_BUTTON | B_BUTTON))
        {
            SwitchToDebugView(taskId);
            HideBg(1);
            ShowBg(0);
            return;
        }
        break;
    }
}

#define sConditionSpriteId data[1]

static void Task_ShowAiParty(u8 taskId)
{
    u32 i, ailment;
    struct WindowTemplate winTemplate;
    struct AiPartyMon *aiMons;
    struct BattleDebugMenu *data = GetStructPtr(taskId);

    switch (data->aiViewState)
    {
    case 0:
        HideBg(0);
        ShowBg(1);

        LoadMonIconPalettes();
        LoadPartyMenuAilmentGfx();
        data->aiBattlerId = data->battlerId;
        aiMons = gAiPartyData->mons[GetBattlerSide(data->aiBattlerId)];
        for (i = 0; i < gAiPartyData->count[GetBattlerSide(data->aiBattlerId)]; i++)
        {
            u16 species = SPECIES_NONE; // Question mark
            if (aiMons[i].wasSentInBattle && aiMons[i].species)
                species = aiMons[i].species;
            data->spriteIds.aiPartyIcons[i] = CreateMonIcon(species, SpriteCallbackDummy, (i * 41) + 15, 7, 1, 0);
            gSprites[data->spriteIds.aiPartyIcons[i]].oam.priority = 0;

            gSprites[data->spriteIds.aiPartyIcons[i]].sConditionSpriteId = CreateSprite(&gSpriteTemplate_StatusIcons, (i * 41) + 15, 7, 0);
            gSprites[gSprites[data->spriteIds.aiPartyIcons[i]].sConditionSpriteId].oam.priority = 0;
            if (aiMons[i].isFainted)
                ailment = AILMENT_FNT;
            else
                ailment = GetAilmentFromStatus(aiMons[i].status);

            if (ailment != AILMENT_NONE)
                StartSpriteAnim(&gSprites[gSprites[data->spriteIds.aiPartyIcons[i]].sConditionSpriteId], ailment - 1);
            else
                gSprites[gSprites[data->spriteIds.aiPartyIcons[i]].sConditionSpriteId].invisible = TRUE;
        }
        for (; i < PARTY_SIZE; i++)
            data->spriteIds.aiPartyIcons[i] = 0xFF;
        data->aiViewState++;
        break;
    // Put text
    case 1:
        winTemplate = CreateWindowTemplate(1, 0, 3, 29, 16, 15, 0x150);
        data->aiMovesWindowId = AddWindow(&winTemplate);
        PutWindowTilemap(data->aiMovesWindowId);
        PutAiPartyText(data);
        data->aiViewState++;
        break;
    // Input
    case 2:
        if (JOY_NEW(SELECT_BUTTON | B_BUTTON))
        {
            SwitchToDebugViewFromAiParty(taskId);
            HideBg(1);
            ShowBg(0);
            return;
        }
        break;
    }
}

static void SwitchToAiInfoView(u8 taskId)
{
    gTasks[taskId].func = Task_ShowAiKnowledge;
    GetStructPtr(taskId)->aiViewState = 0;
}

static void SwitchToAiPartyView(u8 taskId)
{
    gTasks[taskId].func = Task_ShowAiParty;
    GetStructPtr(taskId)->aiViewState = 0;
}

static void SwitchToDebugViewFromAiParty(u8 taskId)
{
    u32 i;
    struct BattleDebugMenu *data = GetStructPtr(taskId);

    FreeMonIconPalettes();
    for (i = 0; i < PARTY_SIZE; i++)
    {
        if (data->spriteIds.aiPartyIcons[i] != 0xFF)
        {
            DestroySpriteAndFreeResources(&gSprites[gSprites[data->spriteIds.aiPartyIcons[i]].sConditionSpriteId]);
            FreeAndDestroyMonIconSprite(&gSprites[data->spriteIds.aiPartyIcons[i]]);
        }
    }
    ClearWindowTilemap(data->aiMovesWindowId);
    RemoveWindow(data->aiMovesWindowId);

    gTasks[taskId].func = Task_DebugMenuProcessInput;
}

#undef sConditionSpriteId

static void SwitchToDebugView(u8 taskId)
{
    CleanUpAiInfoWindow(taskId);
    gTasks[taskId].func = Task_DebugMenuProcessInput;
}

static void Task_DebugMenuFadeIn(u8 taskId)
{
    if (!gPaletteFade.active)
        gTasks[taskId].func = Task_DebugMenuProcessInput;
}

static void Task_DebugMenuProcessInput(u8 taskId)
{
    s32 listItemId = 0;
    struct BattleDebugMenu *data = GetStructPtr(taskId);

    // Exit the menu.
    if (JOY_NEW(SELECT_BUTTON) || ((JOY_NEW(B_BUTTON)) && data->activeWindow == ACTIVE_WIN_MAIN))
    {
        BeginNormalPaletteFade(-1, 0, 0, 0x10, 0);
        gTasks[taskId].func = Task_DebugMenuFadeOut;
        return;
    }

    // Try changing active battler.
    if (JOY_NEW(R_BUTTON))
    {
        if (data->battlerId++ == gBattlersCount - 1)
            data->battlerId = 0;
        UpdateWindowsOnChangedBattler(data);
    }
    else if (JOY_NEW(L_BUTTON))
    {
        if (data->battlerId-- == 0)
            data->battlerId = gBattlersCount - 1;
        UpdateWindowsOnChangedBattler(data);
    }

    // A main list item is active, handle input.
    if (data->activeWindow == ACTIVE_WIN_MAIN)
    {
        listItemId = ListMenu_ProcessInput(data->mainListTaskId);
        if (listItemId != LIST_CANCEL && listItemId != LIST_NOTHING_CHOSEN && listItemId < LIST_ITEM_COUNT)
        {
            if (listItemId == LIST_ITEM_AI_MOVES_PTS && JOY_NEW(A_BUTTON))
            {
                SwitchToAiPointsView(taskId);
                return;
            }
            else if (listItemId == LIST_ITEM_AI_INFO && JOY_NEW(A_BUTTON))
            {
                SwitchToAiInfoView(taskId);
                return;
            }
            else if (listItemId == LIST_ITEM_AI_PARTY && JOY_NEW(A_BUTTON))
            {
                SwitchToAiPartyView(taskId);
                return;
            }
            else if (listItemId == LIST_ITEM_INSTANT_WIN && JOY_NEW(A_BUTTON))
            {
                BattleDebug_WonBattle();
                BeginNormalPaletteFade(-1, 0, 0, 0x10, 0);
                gTasks[taskId].func = Task_DebugMenuFadeOut;
                return;
            }
            data->currentMainListItemId = listItemId;

            // Create the secondary menu list.
            CreateSecondaryListMenu(data);
            PrintSecondaryEntries(data);
            data->activeWindow = ACTIVE_WIN_SECONDARY;
        }
    }
    // Secondary list is active, handle input.
    else if (data->activeWindow == ACTIVE_WIN_SECONDARY)
    {
        listItemId = ListMenu_ProcessInput(data->secondaryListTaskId);
        if (listItemId == LIST_CANCEL)
        {
            DestroyListMenuTask(data->secondaryListTaskId, NULL, NULL);
            ClearStdWindowAndFrameToTransparent(data->secondaryListWindowId, TRUE);
            RemoveWindow(data->secondaryListWindowId);
            data->activeWindow = ACTIVE_WIN_MAIN;
            data->secondaryListTaskId = 0xFF;
        }
        else if (listItemId != LIST_NOTHING_CHOSEN)
        {
            data->currentSecondaryListItemId = listItemId;
            data->modifyWindowId = AddWindow(&sModifyWindowTemplate);
            PutWindowTilemap(data->modifyWindowId);
            CopyWindowToVram(data->modifyWindowId, COPYWIN_FULL);
            SetUpModifyArrows(data);
            PrintDigitChars(data);
            data->activeWindow = ACTIVE_WIN_MODIFY;
        }
    }
    // Handle value modifying.
    else if (data->activeWindow == ACTIVE_WIN_MODIFY)
    {
        if (JOY_NEW(B_BUTTON | A_BUTTON))
        {
            ClearStdWindowAndFrameToTransparent(data->modifyWindowId, TRUE);
            RemoveWindow(data->modifyWindowId);
            DestroyModifyArrows(data);
            data->activeWindow = ACTIVE_WIN_SECONDARY;
        }
        else if (JOY_NEW(DPAD_RIGHT))
        {
            if (data->modifyArrows.currentDigit != (data->modifyArrows.maxDigits - 1))
            {
                data->modifyArrows.currentDigit++;
                gSprites[data->modifyArrows.arrowSpriteId[0]].x2 += 6;
                gSprites[data->modifyArrows.arrowSpriteId[1]].x2 += 6;
            }
        }
        else if (JOY_NEW(DPAD_LEFT))
        {
            if (data->modifyArrows.currentDigit != 0)
            {
                data->modifyArrows.currentDigit--;
                gSprites[data->modifyArrows.arrowSpriteId[0]].x2 -= 6;
                gSprites[data->modifyArrows.arrowSpriteId[1]].x2 -= 6;
            }
        }
        else if (JOY_NEW(DPAD_UP))
        {
            if (TryMoveDigit(&data->modifyArrows, TRUE))
            {
                PrintDigitChars(data);
                UpdateBattlerValue(data);
                PrintSecondaryEntries(data);
            }
        }
        else if (JOY_NEW(DPAD_DOWN))
        {
            if (TryMoveDigit(&data->modifyArrows, FALSE))
            {
                PrintDigitChars(data);
                UpdateBattlerValue(data);
                PrintSecondaryEntries(data);
            }
        }
    }
}

static void Task_DebugMenuFadeOut(u8 taskId)
{
    if (!gPaletteFade.active)
    {
        struct BattleDebugMenu *data = GetStructPtr(taskId);
        DestroyListMenuTask(data->mainListTaskId, 0, 0);
        if (data->secondaryListTaskId != 0xFF)
            DestroyListMenuTask(data->secondaryListTaskId, 0, 0);

        FreeAllWindowBuffers();
        UpdateMonData(data);
        gBattleStruct->debugBattler = data->battlerId;
        Free(data);
        DestroyTask(taskId);
        SetMainCallback2(ReshowBattleScreenAfterMenu);
    }
}

static void PrintOnBattlerWindow(u8 windowId, u8 battlerId)
{
    u8 text[POKEMON_NAME_LENGTH + 10];

    text[0] = CHAR_0 + battlerId;
    text[1] = CHAR_SPACE;
    text[2] = CHAR_HYPHEN;
    text[3] = CHAR_SPACE;
    StringCopy(&text[4], gBattleMons[battlerId].nickname);

    FillWindowPixelBuffer(windowId, 0x11);
    AddTextPrinterParameterized(windowId, FONT_NORMAL, text, 0, 0, 0, NULL);
    CopyWindowToVram(windowId, COPYWIN_FULL);
}

static void UpdateWindowsOnChangedBattler(struct BattleDebugMenu *data)
{
    PrintOnBattlerWindow(data->battlerWindowId, data->battlerId);
    if (data->secondaryListTaskId != 0xFF)
    {
        DestroyListMenuTask(data->secondaryListTaskId, 0, 0);
        RemoveWindow(data->secondaryListWindowId);
        CreateSecondaryListMenu(data);
        data->currentSecondaryListItemId = 0;
        PrintSecondaryEntries(data);
    }
    if (data->activeWindow == ACTIVE_WIN_MODIFY)
    {
        DestroyModifyArrows(data);
        SetUpModifyArrows(data);
        PrintDigitChars(data);
    }
}


static void CreateSecondaryListMenu(struct BattleDebugMenu *data)
{
    struct WindowTemplate winTemplate;
    struct ListMenuTemplate listTemplate;
    u8 itemsCount = 1;

    winTemplate = sSecondaryListWindowTemplate;
    listTemplate = sSecondaryListTemplate;

    switch (data->currentMainListItemId)
    {
    case LIST_ITEM_ABILITY:
        itemsCount = 1;
        break;
    case LIST_ITEM_HELD_ITEM:
        itemsCount = 1;
        break;
    case LIST_ITEM_TYPES:
        itemsCount = 3;
        break;
    case LIST_ITEM_MOVES:
        itemsCount = 5;
        break;
    case LIST_ITEM_PP:
        itemsCount = 4;
        break;
    case LIST_ITEM_STATS:
        listTemplate.items = sStatsListItems;
        itemsCount = ARRAY_COUNT(sStatsListItems);
        break;
    case LIST_ITEM_STAT_STAGES:
        itemsCount = 8;
        break;
    case LIST_ITEM_STATUS1:
        listTemplate.items = sStatus1ListItems;
        itemsCount = ARRAY_COUNT(sStatus1ListItems);
        data->bitfield = sStatus1Bitfield;
        break;
    case LIST_ITEM_VOLATILE:
        listTemplate.items = sVolatileStatusListItems;
        itemsCount = ARRAY_COUNT(sVolatileStatusListItems);
        break;
    case LIST_ITEM_AI:
        listTemplate.items = sAIListItems;
        itemsCount = ARRAY_COUNT(sAIListItems);
        data->bitfield = sAIBitfield;
        break;
    case LIST_ITEM_VARIOUS:
        listTemplate.items = sVariousListItems;
        itemsCount = ARRAY_COUNT(sVariousListItems);
        break;
    case LIST_ITEM_HAZARDS:
        listTemplate.items = sHazardsListItems;
        itemsCount = ARRAY_COUNT(sHazardsListItems);
        break;
    case LIST_ITEM_SIDE_STATUS:
        listTemplate.items = sSideStatusListItems;
        itemsCount = ARRAY_COUNT(sSideStatusListItems);
        break;
    case LIST_ITEM_INSTANT_WIN:
    case LIST_ITEM_AI_MOVES_PTS:
    case LIST_ITEM_AI_INFO:
        return;
    }

    data->secondaryListItemCount = itemsCount;
    data->secondaryListWindowId = AddWindow(&winTemplate);

    listTemplate.totalItems = itemsCount;
    listTemplate.maxShowed = itemsCount;
    if (listTemplate.maxShowed > 7 && !sHasChangeableEntries[data->currentMainListItemId])
        listTemplate.maxShowed = 7;
    listTemplate.windowId = data->secondaryListWindowId;

    data->secondaryListTaskId = ListMenuInit(&listTemplate, 0, 0);
    CopyWindowToVram(data->secondaryListWindowId, COPYWIN_FULL);
}

static void PadString(const u8 *src, u8 *dst)
{
    u32 i;

    for (i = 0; i < 19 && src[i] != EOS; i++)
        dst[i] = src[i];

    for (; i < 19; i++)
        dst[i] = CHAR_SPACE;

    dst[i] = EOS;
}

static const u8 sTextAll[] = _("All");

static void PrintSecondaryEntries(struct BattleDebugMenu *data)
{
    u8 text[20];
    s32 i;
    struct TextPrinterTemplate printer;
    u8 yMultiplier;

    // Do not print entries if they are not changing.
    if (!sHasChangeableEntries[data->currentMainListItemId])
        return;

    yMultiplier = (GetFontAttribute(sSecondaryListTemplate.fontId, 1) + sSecondaryListTemplate.itemVerticalPadding);

    printer.windowId = data->secondaryListWindowId;
    printer.fontId = 1;
    printer.unk = 0;
    printer.letterSpacing = 0;
    printer.lineSpacing = 1;
    printer.fgColor = 2;
    printer.bgColor = 1;
    printer.shadowColor = 3;
    printer.x = sSecondaryListTemplate.item_X;
    printer.currentX = sSecondaryListTemplate.item_X;
    printer.currentChar = text;

    switch (data->currentMainListItemId)
    {
    case LIST_ITEM_MOVES:
    case LIST_ITEM_PP:
        for (i = 0; i < 4; i++)
        {
            PadString(GetMoveName(gBattleMons[data->battlerId].moves[i]), text);
            printer.currentY = printer.y = (i * yMultiplier) + sSecondaryListTemplate.upText_Y;
            AddTextPrinter(&printer, 0, NULL);
        }
        // Allow changing all moves at once. Useful for testing in wild doubles.
        if (data->currentMainListItemId == LIST_ITEM_MOVES)
        {
            PadString(sTextAll, text);
            printer.currentY = printer.y = (i * yMultiplier) + sSecondaryListTemplate.upText_Y;
            AddTextPrinter(&printer, 0, NULL);
        }
        break;
    case LIST_ITEM_ABILITY:
        PadString(gAbilitiesInfo[gBattleMons[data->battlerId].ability].name, text);
        printer.currentY = printer.y = sSecondaryListTemplate.upText_Y;
        AddTextPrinter(&printer, 0, NULL);
        break;
    case LIST_ITEM_HELD_ITEM:
        PadString(GetItemName(gBattleMons[data->battlerId].item), text);
        printer.currentY = printer.y = sSecondaryListTemplate.upText_Y;
        AddTextPrinter(&printer, 0, NULL);
        break;
    case LIST_ITEM_TYPES:
        for (i = 0; i < 3; i++)
        {
            u8 *types = &gBattleMons[data->battlerId].types[0];

            PadString(gTypesInfo[types[i]].name, text);
            printer.currentY = printer.y = (i * yMultiplier) + sSecondaryListTemplate.upText_Y;
            AddTextPrinter(&printer, 0, NULL);
        }
        break;
    case LIST_ITEM_STAT_STAGES:
        for (i = 0; i < NUM_BATTLE_STATS - 1; i++)
        {
            u8 *txtPtr = StringCopy(text, gStatNamesTable[STAT_ATK + i]);
            txtPtr[0] = CHAR_SPACE;
            if (gBattleMons[data->battlerId].statStages[STAT_ATK + i] >= DEFAULT_STAT_STAGE)
            {
                txtPtr[1] = CHAR_PLUS;
                txtPtr[2] = CHAR_0 + (gBattleMons[data->battlerId].statStages[STAT_ATK + i] - DEFAULT_STAT_STAGE);
            }
            else
            {
                txtPtr[1] = CHAR_HYPHEN;
                txtPtr[2] = CHAR_6 - (gBattleMons[data->battlerId].statStages[STAT_ATK + i]);
            }
            txtPtr[3] = EOS;

            PadString(text, text);
            printer.currentY = printer.y = (i * yMultiplier) + sSecondaryListTemplate.upText_Y;
            AddTextPrinter(&printer, 0, NULL);
        }
        // Allow changing all stat stages at once.
        PadString(sTextAll, text);
        printer.currentY = printer.y = (i * yMultiplier) + sSecondaryListTemplate.upText_Y;
        AddTextPrinter(&printer, 0, NULL);
        break;
    }
}

static void DestroyModifyArrows(struct BattleDebugMenu *data)
{
    if (data->modifyArrows.arrowSpriteId[0] != 0xFF)
        DestroySpriteAndFreeResources(&gSprites[data->modifyArrows.arrowSpriteId[0]]);
    if (data->modifyArrows.arrowSpriteId[1] != 0xFF)
        DestroySpriteAndFreeResources(&gSprites[data->modifyArrows.arrowSpriteId[1]]);
}

static void PrintDigitChars(struct BattleDebugMenu *data)
{
    s32 i;
    u8 text[MAX_MODIFY_DIGITS + 1];

    for (i = 0; i < data->modifyArrows.maxDigits; i++)
        text[i] = data->modifyArrows.charDigits[i];

    text[i] = EOS;

    FillWindowPixelBuffer(data->modifyWindowId, 0x11);
    AddTextPrinterParameterized(data->modifyWindowId, FONT_NORMAL, text, 3, 0, 0, NULL);
}

static const u32 GetBitfieldToAndValue(u32 currBit, u32 bitsCount)
{
    u32 i;
    u32 toAnd = 0;

    for (i = 0; i < bitsCount; i++)
        toAnd |= (1 << (currBit + i));

    return toAnd;
}

static const u32 GetBitfieldValue(u32 value, u32 currBit, u32 bitsCount)
{
    return (value & (GetBitfieldToAndValue(currBit, bitsCount))) >> currBit;
}

static void UpdateBattlerValue(struct BattleDebugMenu *data)
{
    u32 i;
    switch (data->modifyArrows.typeOfVal)
    {
    case VAL_U8:
        *(u8 *)(data->modifyArrows.modifiedValPtr) = data->modifyArrows.currValue;
        break;
    case VAL_S8:
        *(s8 *)(data->modifyArrows.modifiedValPtr) = data->modifyArrows.currValue;
        break;
    case VAL_U16:
        *(u16 *)(data->modifyArrows.modifiedValPtr) = data->modifyArrows.currValue;
        break;
    case VAR_U16_4_ENTRIES:
        ((u16 *)(data->modifyArrows.modifiedValPtr))[0] = data->modifyArrows.currValue;
        ((u16 *)(data->modifyArrows.modifiedValPtr))[1] = data->modifyArrows.currValue;
        ((u16 *)(data->modifyArrows.modifiedValPtr))[2] = data->modifyArrows.currValue;
        ((u16 *)(data->modifyArrows.modifiedValPtr))[3] = data->modifyArrows.currValue;
        break;
    case VAL_ALL_STAT_STAGES:
        for (i = 0; i < NUM_BATTLE_STATS; i++)
            gBattleMons[data->battlerId].statStages[i] = data->modifyArrows.currValue;
        break;
    case VAL_U32:
        *(u32 *)(data->modifyArrows.modifiedValPtr) = data->modifyArrows.currValue;
        break;
    case VAL_BITFIELD_32:
        *(u32 *)(data->modifyArrows.modifiedValPtr) &= ~(GetBitfieldToAndValue(data->bitfield[data->currentSecondaryListItemId].currBit, data->bitfield[data->currentSecondaryListItemId].bitsCount));
        *(u32 *)(data->modifyArrows.modifiedValPtr) |= (data->modifyArrows.currValue << data->bitfield[data->currentSecondaryListItemId].currBit);
        break;
    case VAL_VOLATILE:
        SetMonVolatile(data->battlerId, data->currentSecondaryListItemId, data->modifyArrows.currValue);
        break;
    case VAL_HAZARDS:
        ChangeHazardsValue(data);
        break;
    case VAR_SIDE_STATUS:
        *GetSideStatusValue(data, TRUE, data->modifyArrows.currValue != 0) = data->modifyArrows.currValue;
        break;
    case VAR_SHOW_HP:
        (*(struct BattleSpriteInfo*)(data->modifyArrows.modifiedValPtr)).hpNumbersNoBars = data->modifyArrows.currValue;
        break;
    case VAR_SUBSTITUTE:
        *(u8 *)(data->modifyArrows.modifiedValPtr) = data->modifyArrows.currValue;
        if (*(u8 *)(data->modifyArrows.modifiedValPtr) == 0)
        {
            gBattleMons[data->battlerId].volatiles.substitute = FALSE;
            gBattleSpritesDataPtr->battlerData[data->battlerId].behindSubstitute = 0;
        }
        else
        {
            gBattleMons[data->battlerId].volatiles.substitute = TRUE;
            gBattleSpritesDataPtr->battlerData[data->battlerId].behindSubstitute = 1;
        }
        break;
    case VAR_IN_LOVE:
        if (data->modifyArrows.currValue)
        {
            if (IsBattlerAlive(BATTLE_OPPOSITE(data->battlerId)))
                gBattleMons[data->battlerId].volatiles.infatuation = INFATUATED_WITH(BATTLE_OPPOSITE(data->battlerId));
            else
                gBattleMons[data->battlerId].volatiles.infatuation = INFATUATED_WITH(BATTLE_PARTNER(BATTLE_OPPOSITE(data->battlerId)));
        }
        else
        {
            gBattleMons[data->battlerId].volatiles.infatuation = 0;
        }
        break;
    }
    data->battlerWasChanged[data->battlerId] = TRUE;
}

static u32 CharDigitsToValue(u8 *charDigits, u8 maxDigits)
{
    s32 i;
    u8 id = 0;
    u32 newValue = 0;
    u8 valueDigits[MAX_MODIFY_DIGITS];

    for (i = 0; i < MAX_MODIFY_DIGITS; i++)
        valueDigits[i] = charDigits[i] - CHAR_0;

    if (maxDigits >= MAX_MODIFY_DIGITS)
        newValue += valueDigits[id++] * 1000;
    if (maxDigits >= MAX_MODIFY_DIGITS - 1)
        newValue += valueDigits[id++] * 100;
    if (maxDigits >= MAX_MODIFY_DIGITS - 2)
        newValue += valueDigits[id++] * 10;
    if (maxDigits >= MAX_MODIFY_DIGITS - 3)
        newValue += valueDigits[id++];

    return newValue;
}

static void ValueToCharDigits(u8 *charDigits, u32 newValue, u8 maxDigits)
{
    s32 i;
    u8 valueDigits[MAX_MODIFY_DIGITS];
    u8 id = 0;

    if (maxDigits >= MAX_MODIFY_DIGITS)
        valueDigits[id++] = newValue / 1000;
    if (maxDigits >= MAX_MODIFY_DIGITS - 1)
        valueDigits[id++] = (newValue % 1000) / 100;
    if (maxDigits >= MAX_MODIFY_DIGITS - 2)
        valueDigits[id++] = (newValue % 100) / 10;
    if (maxDigits >= MAX_MODIFY_DIGITS - 3)
        valueDigits[id++] = newValue % 10;

    for (i = 0; i < MAX_MODIFY_DIGITS; i++)
        charDigits[i] = valueDigits[i] + CHAR_0;
}

static void ChangeHazardsValue(struct BattleDebugMenu *data)
{
    u32 side = GetBattlerSide(data->battlerId);

    switch (data->currentSecondaryListItemId)
    {
    case LIST_SIDE_SPIKES:
        if (data->modifyArrows.currValue > 0)
        {
            if (gSideTimers[side].spikesAmount == 0)
                PushHazardTypeToQueue(side, HAZARDS_SPIKES);
            gSideTimers[side].spikesAmount = data->modifyArrows.currValue;
        }
        else if (data->modifyArrows.currValue == 0)
        {
            gSideTimers[side].spikesAmount = 0;
            RemoveHazardFromField(side, HAZARDS_SPIKES);
        }
        break;
    case LIST_SIDE_TOXIC_SPIKES:
        if (data->modifyArrows.currValue > 0)
        {
            if (gSideTimers[side].toxicSpikesAmount == 0)
                PushHazardTypeToQueue(side, HAZARDS_TOXIC_SPIKES);
            gSideTimers[side].toxicSpikesAmount = data->modifyArrows.currValue;
        }
        else if (data->modifyArrows.currValue == 0)
        {
            gSideTimers[side].toxicSpikesAmount = 0;
            RemoveHazardFromField(side, HAZARDS_TOXIC_SPIKES);
        }
        break;
    case LIST_SIDE_STICKY_WEB:
        if (data->modifyArrows.currValue > 0)
            PushHazardTypeToQueue(side, HAZARDS_STICKY_WEB);
        else if (data->modifyArrows.currValue == 0)
            RemoveHazardFromField(side, HAZARDS_STICKY_WEB);
        break;
    case LIST_SIDE_STEALTH_ROCK:
        if (data->modifyArrows.currValue > 0)
            PushHazardTypeToQueue(side, HAZARDS_STEALTH_ROCK);
        else if (data->modifyArrows.currValue == 0)
            RemoveHazardFromField(side, HAZARDS_STEALTH_ROCK);
        break;
    case LIST_SIDE_STEELSURGE:
        if (data->modifyArrows.currValue > 0)
            PushHazardTypeToQueue(side, HAZARDS_STEELSURGE);
        else if (data->modifyArrows.currValue == 0)
            RemoveHazardFromField(side, HAZARDS_STEELSURGE);
        break;
    }
}

static u32 GetHazardsValue(struct BattleDebugMenu *data)
{
    u32 hazardsLayers = 0;
    switch (data->currentSecondaryListItemId)
    {
    case LIST_SIDE_SPIKES:
        hazardsLayers = gSideTimers[GetBattlerSide(data->battlerId)].spikesAmount;
        break;
    case LIST_SIDE_TOXIC_SPIKES:
        hazardsLayers = gSideTimers[GetBattlerSide(data->battlerId)].toxicSpikesAmount;
        break;
    case LIST_SIDE_STICKY_WEB:
        hazardsLayers = IsHazardOnSide(GetBattlerSide(data->battlerId), HAZARDS_STICKY_WEB);
        break;
    case LIST_SIDE_STEALTH_ROCK:
        hazardsLayers = IsHazardOnSide(GetBattlerSide(data->battlerId), HAZARDS_STEALTH_ROCK);
        break;
    case LIST_SIDE_STEELSURGE:
        hazardsLayers = IsHazardOnSide(GetBattlerSide(data->battlerId), HAZARDS_STEELSURGE);
        break;
    }
    return hazardsLayers;
}

static u16 *GetSideStatusValue(struct BattleDebugMenu *data, bool32 changeStatus, bool32 statusTrue)
{
    struct SideTimer *sideTimer = &gSideTimers[GetBattlerSide(data->battlerId)];

    switch (data->currentSecondaryListItemId)
    {
    case LIST_SIDE_REFLECT:
        if (changeStatus)
        {
            if (statusTrue)
                *(u32 *)(data->modifyArrows.modifiedValPtr) |= SIDE_STATUS_REFLECT;
            else
                *(u32 *)(data->modifyArrows.modifiedValPtr) &= ~SIDE_STATUS_REFLECT;
        }
        return &sideTimer->reflectTimer;
    case LIST_SIDE_LIGHTSCREEN:
        if (changeStatus)
        {
            if (statusTrue)
                *(u32 *)(data->modifyArrows.modifiedValPtr) |= SIDE_STATUS_LIGHTSCREEN;
            else
                *(u32 *)(data->modifyArrows.modifiedValPtr) &= ~SIDE_STATUS_LIGHTSCREEN;
        }
        return &sideTimer->lightscreenTimer;
    case LIST_SIDE_SAFEGUARD:
        if (changeStatus)
        {
            if (statusTrue)
                *(u32 *)(data->modifyArrows.modifiedValPtr) |= SIDE_STATUS_SAFEGUARD;
            else
                *(u32 *)(data->modifyArrows.modifiedValPtr) &= ~SIDE_STATUS_SAFEGUARD;
        }
        return &sideTimer->safeguardTimer;
    case LIST_SIDE_MIST:
        if (changeStatus)
        {
            if (statusTrue)
                *(u32 *)(data->modifyArrows.modifiedValPtr) |= SIDE_STATUS_MIST;
            else
                *(u32 *)(data->modifyArrows.modifiedValPtr) &= ~SIDE_STATUS_MIST;
        }
        return &sideTimer->mistTimer;
    case LIST_SIDE_TAILWIND:
        if (changeStatus)
        {
            if (statusTrue)
                *(u32 *)(data->modifyArrows.modifiedValPtr) |= SIDE_STATUS_TAILWIND;
            else
                *(u32 *)(data->modifyArrows.modifiedValPtr) &= ~SIDE_STATUS_TAILWIND;
        }
        return &sideTimer->tailwindTimer;
    case LIST_SIDE_AURORA_VEIL:
        if (changeStatus)
        {
            if (statusTrue)
                *(u32 *)(data->modifyArrows.modifiedValPtr) |= SIDE_STATUS_AURORA_VEIL;
            else
                *(u32 *)(data->modifyArrows.modifiedValPtr) &= ~SIDE_STATUS_AURORA_VEIL;
        }
        return &sideTimer->auroraVeilTimer;
    case LIST_SIDE_LUCKY_CHANT:
        if (changeStatus)
        {
            if (statusTrue)
                *(u32 *)(data->modifyArrows.modifiedValPtr) |= SIDE_STATUS_LUCKY_CHANT;
            else
                *(u32 *)(data->modifyArrows.modifiedValPtr) &= ~SIDE_STATUS_LUCKY_CHANT;
        }
        return &sideTimer->luckyChantTimer;
    case LIST_SIDE_DAMAGE_NON_TYPES:
        if (changeStatus)
        {
            if (statusTrue)
                *(u32 *)(data->modifyArrows.modifiedValPtr) |= SIDE_STATUS_DAMAGE_NON_TYPES;
            else
                *(u32 *)(data->modifyArrows.modifiedValPtr) &= ~SIDE_STATUS_DAMAGE_NON_TYPES;
            sideTimer->damageNonTypesType = GetMoveType(gCurrentMove);
        }
        return &sideTimer->damageNonTypesTimer;
    case LIST_SIDE_RAINBOW:
        if (changeStatus)
        {
            if (statusTrue)
                *(u32 *)(data->modifyArrows.modifiedValPtr) |= SIDE_STATUS_RAINBOW;
            else
                *(u32 *)(data->modifyArrows.modifiedValPtr) &= ~SIDE_STATUS_RAINBOW;
        }
        return &sideTimer->rainbowTimer;
    case LIST_SIDE_SEA_OF_FIRE:
        if (changeStatus)
        {
            if (statusTrue)
                *(u32 *)(data->modifyArrows.modifiedValPtr) |= SIDE_STATUS_SEA_OF_FIRE;
            else
                *(u32 *)(data->modifyArrows.modifiedValPtr) &= ~SIDE_STATUS_SEA_OF_FIRE;
        }
        return &sideTimer->seaOfFireTimer;
    case LIST_SIDE_SWAMP:
        if (changeStatus)
        {
            if (statusTrue)
                *(u32 *)(data->modifyArrows.modifiedValPtr) |= SIDE_STATUS_SWAMP;
            else
                *(u32 *)(data->modifyArrows.modifiedValPtr) &= ~SIDE_STATUS_SWAMP;
        }
        return &sideTimer->swampTimer;
    default:
        return NULL;
    }
}

static void SetUpModifyArrows(struct BattleDebugMenu *data)
{
    LoadSpritePalette(&gSpritePalette_Arrow);
    data->modifyArrows.arrowSpriteId[0] = CreateSprite(&gSpriteTemplate_Arrow, 207, 12, 0);
    data->modifyArrows.arrowSpriteId[1] = CreateSprite(&gSpriteTemplate_Arrow, 207, 36, 0);
    gSprites[data->modifyArrows.arrowSpriteId[1]].animNum = 1;
    switch (data->currentMainListItemId)
    {
    case LIST_ITEM_ABILITY:
        data->modifyArrows.minValue = 0;
        data->modifyArrows.maxValue = ABILITIES_COUNT - 1;
        data->modifyArrows.maxDigits = 3;
        data->modifyArrows.modifiedValPtr = &gBattleMons[data->battlerId].ability;
        data->modifyArrows.typeOfVal = VAL_U16;
        data->modifyArrows.currValue = gBattleMons[data->battlerId].ability;
        break;
    case LIST_ITEM_MOVES:
        data->modifyArrows.minValue = 0;
        data->modifyArrows.maxValue = MOVES_COUNT - 1;
        data->modifyArrows.maxDigits = 3;
        if (data->currentSecondaryListItemId == 4)
        {
            data->modifyArrows.modifiedValPtr = &gBattleMons[data->battlerId].moves[0];
            data->modifyArrows.currValue = gBattleMons[data->battlerId].moves[0];
            data->modifyArrows.typeOfVal = VAR_U16_4_ENTRIES;
        }
        else
        {
            data->modifyArrows.modifiedValPtr = &gBattleMons[data->battlerId].moves[data->currentSecondaryListItemId];
            data->modifyArrows.currValue = gBattleMons[data->battlerId].moves[data->currentSecondaryListItemId];
            data->modifyArrows.typeOfVal = VAL_U16;
        }
        break;
    case LIST_ITEM_PP:
        data->modifyArrows.minValue = 0;
        data->modifyArrows.maxValue = CalculatePPWithBonus(gBattleMons[data->battlerId].moves[data->currentSecondaryListItemId], gBattleMons[data->battlerId].ppBonuses, data->currentSecondaryListItemId);
        data->modifyArrows.maxDigits = 2;
        data->modifyArrows.modifiedValPtr = &gBattleMons[data->battlerId].pp[data->currentSecondaryListItemId];
        data->modifyArrows.typeOfVal = VAL_U8;
        data->modifyArrows.currValue = gBattleMons[data->battlerId].pp[data->currentSecondaryListItemId];
        break;
    case LIST_ITEM_HELD_ITEM:
        data->modifyArrows.minValue = 0;
        data->modifyArrows.maxValue = ITEMS_COUNT - 1;
        data->modifyArrows.maxDigits = 3;
        data->modifyArrows.modifiedValPtr = &gBattleMons[data->battlerId].item;
        data->modifyArrows.typeOfVal = VAL_U16;
        data->modifyArrows.currValue = gBattleMons[data->battlerId].item;
        break;
    case LIST_ITEM_TYPES:
        data->modifyArrows.minValue = 0;
        data->modifyArrows.maxValue = NUMBER_OF_MON_TYPES - 1;
        data->modifyArrows.maxDigits = 2;
        data->modifyArrows.modifiedValPtr = (u8 *)((&gBattleMons[data->battlerId].types[0]) + data->currentSecondaryListItemId);
        data->modifyArrows.typeOfVal = VAL_U8;
        data->modifyArrows.currValue = *(u8 *)((&gBattleMons[data->battlerId].types[0]) + data->currentSecondaryListItemId);
        break;
    case LIST_ITEM_STATS:
        data->modifyArrows.minValue = 0;
        data->modifyArrows.maxValue = 9999;
        data->modifyArrows.maxDigits = 4;
        data->modifyArrows.typeOfVal = VAL_U16;
        if (data->currentSecondaryListItemId == LIST_STAT_HP_CURRENT)
        {
            data->modifyArrows.modifiedValPtr = &gBattleMons[data->battlerId].hp;
            data->modifyArrows.currValue = gBattleMons[data->battlerId].hp;
            data->modifyArrows.minValue = 1;
            data->modifyArrows.maxValue = gBattleMons[data->battlerId].maxHP;
        }
        else if (data->currentSecondaryListItemId == LIST_STAT_HP_MAX)
        {
            data->modifyArrows.modifiedValPtr = &gBattleMons[data->battlerId].maxHP;
            data->modifyArrows.minValue = gBattleMons[data->battlerId].hp;
            data->modifyArrows.currValue = gBattleMons[data->battlerId].maxHP;
        }
        else
        {
            data->modifyArrows.modifiedValPtr = (u16 *)((&gBattleMons[data->battlerId].attack) + (data->currentSecondaryListItemId - 2));
            data->modifyArrows.currValue = *(u16 *)((&gBattleMons[data->battlerId].attack) + (data->currentSecondaryListItemId - 2));
        }
        break;
    case LIST_ITEM_STAT_STAGES:
        data->modifyArrows.minValue = 0;
        data->modifyArrows.maxValue = 12;
        data->modifyArrows.maxDigits = 2;
        if (data->currentSecondaryListItemId == NUM_BATTLE_STATS - 1) // Change all stats
        {
            data->modifyArrows.modifiedValPtr = &gBattleMons[data->battlerId].statStages[STAT_ATK];
            data->modifyArrows.currValue = gBattleMons[data->battlerId].statStages[STAT_ATK];
            data->modifyArrows.typeOfVal = VAL_ALL_STAT_STAGES;
        }
        else
        {
            data->modifyArrows.modifiedValPtr = &gBattleMons[data->battlerId].statStages[data->currentSecondaryListItemId + STAT_ATK];
            data->modifyArrows.typeOfVal = VAL_U8;
            data->modifyArrows.currValue = gBattleMons[data->battlerId].statStages[data->currentSecondaryListItemId + STAT_ATK];
        }
        break;
    case LIST_ITEM_VARIOUS:
        if (data->currentSecondaryListItemId == VARIOUS_SHOW_HP)
        {
            data->modifyArrows.minValue = 0;
            data->modifyArrows.maxValue = 1;
            data->modifyArrows.maxDigits = 1;
            data->modifyArrows.modifiedValPtr = &gBattleSpritesDataPtr->battlerData[data->battlerId];
            data->modifyArrows.typeOfVal = VAR_SHOW_HP;
            data->modifyArrows.currValue = gBattleSpritesDataPtr->battlerData[data->battlerId].hpNumbersNoBars;
        }
        else if (data->currentSecondaryListItemId == VARIOUS_SUBSTITUTE_HP)
        {
            data->modifyArrows.minValue = 0;
            data->modifyArrows.maxValue = 255;
            data->modifyArrows.maxDigits = 3;
            data->modifyArrows.modifiedValPtr = &gDisableStructs[data->battlerId].substituteHP;
            data->modifyArrows.typeOfVal = VAR_SUBSTITUTE;
            data->modifyArrows.currValue = gDisableStructs[data->battlerId].substituteHP;
        }
        else if (data->currentSecondaryListItemId == VARIOUS_IN_LOVE)
        {
            data->modifyArrows.minValue = 0;
            data->modifyArrows.maxValue = 1;
            data->modifyArrows.maxDigits = 1;
            data->modifyArrows.modifiedValPtr = NULL;
            data->modifyArrows.typeOfVal = VAR_IN_LOVE;
            data->modifyArrows.currValue = gBattleMons[data->battlerId].volatiles.infatuation;
        }
        break;
    case LIST_ITEM_STATUS1:
        data->modifyArrows.modifiedValPtr = &gBattleMons[data->battlerId].status1;
        data->modifyArrows.currValue = GetBitfieldValue(gBattleMons[data->battlerId].status1, data->bitfield[data->currentSecondaryListItemId].currBit, data->bitfield[data->currentSecondaryListItemId].bitsCount);
        data->modifyArrows.typeOfVal = VAL_BITFIELD_32;
        goto CASE_ITEM_STATUS;
    case LIST_ITEM_VOLATILE:
        data->modifyArrows.currValue = GetBattlerVolatile(data->battlerId, data->currentSecondaryListItemId);
        data->modifyArrows.typeOfVal = VAL_VOLATILE;
        data->modifyArrows.minValue = 0;
#define UNPACK_VOLATILE_MAX_SIZE(_enum, _fieldName, _typeMaxValue, ...) case _enum: data->modifyArrows.maxValue = min(MAX_u16, GET_VOLATILE_MAXIMUM(_typeMaxValue)); break;
        switch (data->currentSecondaryListItemId)
        {
            VOLATILE_DEFINITIONS(UNPACK_VOLATILE_MAX_SIZE)
            /* Expands to the following:
             * case VOLATILE_CONFUSION:
                  data->modifyArrows.maxValue = MAX_BITS(3); // Max value 7
                  break;
             * case VOLATILE_FLINCHED:
                  data->modifyArrows.maxValue = MAX_BITS(1); // Max value 1
                  break;
             * ...etc.
             */
            default:
                data->modifyArrows.maxValue = 0;
        }
        data->modifyArrows.maxDigits = MAX_DIGITS(data->modifyArrows.maxValue);
        break;
    case LIST_ITEM_AI:
        data->modifyArrows.modifiedValPtr = &gAiThinkingStruct->aiFlags[data->battlerId];
        data->modifyArrows.currValue = GetBitfieldValue(gAiThinkingStruct->aiFlags[data->battlerId], data->bitfield[data->currentSecondaryListItemId].currBit, data->bitfield[data->currentSecondaryListItemId].bitsCount);
        data->modifyArrows.typeOfVal = VAL_BITFIELD_32;
        goto CASE_ITEM_STATUS;
    CASE_ITEM_STATUS:
        data->modifyArrows.minValue = 0;
        data->modifyArrows.maxValue = (1 << data->bitfield[data->currentSecondaryListItemId].bitsCount) - 1;
        data->modifyArrows.maxDigits = MAX_DIGITS(data->modifyArrows.maxValue);
        break;
    case LIST_ITEM_HAZARDS:
        data->modifyArrows.minValue = 0;
        switch (data->currentSecondaryListItemId)
        {
        case LIST_SIDE_SPIKES:
            data->modifyArrows.maxValue = 3;
            break;
        case LIST_SIDE_TOXIC_SPIKES:
            data->modifyArrows.maxValue = 2;
            break;
        case LIST_SIDE_STICKY_WEB:
        case LIST_SIDE_STEALTH_ROCK:
        case LIST_SIDE_STEELSURGE:
            data->modifyArrows.maxValue = 1;
            break;
        }
        data->modifyArrows.maxDigits = 2;
        data->modifyArrows.typeOfVal = VAL_HAZARDS;
        data->modifyArrows.currValue = GetHazardsValue(data);
        break;
    case LIST_ITEM_SIDE_STATUS:
        data->modifyArrows.minValue = 0;
        data->modifyArrows.maxValue = 9;
        data->modifyArrows.maxDigits = 2;
        data->modifyArrows.modifiedValPtr = &gSideStatuses[GetBattlerSide(data->battlerId)];
        data->modifyArrows.typeOfVal = VAR_SIDE_STATUS;
        data->modifyArrows.currValue = *GetSideStatusValue(data, FALSE, FALSE);
        break;
    }

    data->modifyArrows.currentDigit = 0;
    ValueToCharDigits(data->modifyArrows.charDigits, data->modifyArrows.currValue, data->modifyArrows.maxDigits);
}

static bool32 TryMoveDigit(struct BattleDebugModifyArrows *modArrows, bool32 moveUp)
{
    s32 i;
    u8 charDigits[MAX_MODIFY_DIGITS];
    u32 newValue;

    for (i = 0; i < MAX_MODIFY_DIGITS; i++)
        charDigits[i] = modArrows->charDigits[i];

    if (moveUp)
    {
        if (charDigits[modArrows->currentDigit] == CHAR_9)
        {
            charDigits[modArrows->currentDigit] = CHAR_0;
            for (i = modArrows->currentDigit - 1; i >= 0; i--)
            {
                if (charDigits[i] == CHAR_9)
                {
                    charDigits[i] = CHAR_0;
                }
                else
                {
                    charDigits[i]++;
                    break;
                }
            }
        }
        else
            charDigits[modArrows->currentDigit]++;
    }
    else
    {
        if (charDigits[modArrows->currentDigit] == CHAR_0)
        {
            charDigits[modArrows->currentDigit] = CHAR_9;
            for (i = modArrows->currentDigit - 1; i >= 0; i--)
            {
                if (charDigits[i] == CHAR_0)
                {
                    charDigits[i] = CHAR_9;
                }
                else
                {
                    charDigits[i]--;
                    break;
                }
            }
        }
        else
            charDigits[modArrows->currentDigit]--;
    }

    newValue = CharDigitsToValue(charDigits, modArrows->maxDigits);
    if (newValue > modArrows->maxValue || newValue < modArrows->minValue)
    {
        return FALSE;
    }
    else
    {
        modArrows->currValue = newValue;
        for (i = 0; i < MAX_MODIFY_DIGITS; i++)
             modArrows->charDigits[i] = charDigits[i];
        return TRUE;
    }
}

static void UpdateMonData(struct BattleDebugMenu *data)
{
    s32 i, j;

    for (i = 0; i < MAX_BATTLERS_COUNT; i++)
    {
        if (data->battlerWasChanged[i])
        {
            struct Pokemon *mon = GetBattlerMon(i);
            struct BattlePokemon *battleMon = &gBattleMons[i];

            SetMonData(mon, MON_DATA_HELD_ITEM, &battleMon->item);
            SetMonData(mon, MON_DATA_STATUS, &battleMon->status1);
            SetMonData(mon, MON_DATA_HP, &battleMon->hp);
            SetMonData(mon, MON_DATA_MAX_HP, &battleMon->maxHP);
            for (j = 0; j < 4; j++)
                SetMonData(mon, MON_DATA_MOVE1 + j, &battleMon->moves[j]);
        }
    }
}

static const u8 *const sHoldEffectNames[HOLD_EFFECT_COUNT] =
{
    [HOLD_EFFECT_NONE]             = COMPOUND_STRING("????????"),
    [HOLD_EFFECT_RESTORE_HP]       = COMPOUND_STRING("Restore Hp"),
    [HOLD_EFFECT_CURE_PAR]         = COMPOUND_STRING("Cure Par"),
    [HOLD_EFFECT_CURE_SLP]         = COMPOUND_STRING("Cure Slp"),
    [HOLD_EFFECT_CURE_PSN]         = COMPOUND_STRING("Cure Psn"),
    [HOLD_EFFECT_CURE_BRN]         = COMPOUND_STRING("Cure Brn"),
    [HOLD_EFFECT_CURE_FRZ]         = COMPOUND_STRING("Cure Frz"),
    [HOLD_EFFECT_RESTORE_PP]       = COMPOUND_STRING("Restore Pp"),
    [HOLD_EFFECT_CURE_CONFUSION]   = COMPOUND_STRING("Cure Confusion"),
    [HOLD_EFFECT_CURE_STATUS]      = COMPOUND_STRING("Cure Status"),
    [HOLD_EFFECT_CONFUSE_SPICY]    = COMPOUND_STRING("Confuse Spicy"),
    [HOLD_EFFECT_CONFUSE_DRY]      = COMPOUND_STRING("Confuse Dry"),
    [HOLD_EFFECT_CONFUSE_SWEET]    = COMPOUND_STRING("Confuse Sweet"),
    [HOLD_EFFECT_CONFUSE_BITTER]   = COMPOUND_STRING("Confuse Bitter"),
    [HOLD_EFFECT_CONFUSE_SOUR]     = COMPOUND_STRING("Confuse Sour"),
    [HOLD_EFFECT_ATTACK_UP]        = COMPOUND_STRING("Attack Up"),
    [HOLD_EFFECT_DEFENSE_UP]       = COMPOUND_STRING("Defense Up"),
    [HOLD_EFFECT_SPEED_UP]         = COMPOUND_STRING("Speed Up"),
    [HOLD_EFFECT_SP_ATTACK_UP]     = COMPOUND_STRING("Sp Attack Up"),
    [HOLD_EFFECT_SP_DEFENSE_UP]    = COMPOUND_STRING("Sp Defense Up"),
    [HOLD_EFFECT_CRITICAL_UP]      = COMPOUND_STRING("Critical Up"),
    [HOLD_EFFECT_RANDOM_STAT_UP]   = COMPOUND_STRING("Random Stat Up"),
    [HOLD_EFFECT_EVASION_UP]       = COMPOUND_STRING("Evasion Up"),
    [HOLD_EFFECT_WHITE_HERB]       = COMPOUND_STRING("Restore Stats"),
    [HOLD_EFFECT_MACHO_BRACE]      = COMPOUND_STRING("Macho Brace"),
    [HOLD_EFFECT_EXP_SHARE]        = COMPOUND_STRING("Exp Share"),
    [HOLD_EFFECT_QUICK_CLAW]       = COMPOUND_STRING("Quick Claw"),
    [HOLD_EFFECT_FRIENDSHIP_UP]    = COMPOUND_STRING("Friendship Up"),
    [HOLD_EFFECT_MENTAL_HERB]      = COMPOUND_STRING("Mental Herb"),
    [HOLD_EFFECT_CHOICE_BAND]      = COMPOUND_STRING("Choice Band"),
    [HOLD_EFFECT_FLINCH]           = COMPOUND_STRING("Flinch"),
    [HOLD_EFFECT_DOUBLE_PRIZE]     = COMPOUND_STRING("Double Prize"),
    [HOLD_EFFECT_REPEL]            = COMPOUND_STRING("Repel"),
    [HOLD_EFFECT_SOUL_DEW]         = COMPOUND_STRING("Soul Dew"),
    [HOLD_EFFECT_DEEP_SEA_TOOTH]   = COMPOUND_STRING("Deep Sea Tooth"),
    [HOLD_EFFECT_DEEP_SEA_SCALE]   = COMPOUND_STRING("Deep Sea Scale"),
    [HOLD_EFFECT_CAN_ALWAYS_RUN]   = COMPOUND_STRING("Can Always Run"),
    [HOLD_EFFECT_PREVENT_EVOLVE]   = COMPOUND_STRING("Prevent Evolve"),
    [HOLD_EFFECT_FOCUS_BAND]       = COMPOUND_STRING("Focus Band"),
    [HOLD_EFFECT_LUCKY_EGG]        = COMPOUND_STRING("Lucky Egg"),
    [HOLD_EFFECT_SCOPE_LENS]       = COMPOUND_STRING("Scope Lens"),
    [HOLD_EFFECT_LEFTOVERS]        = COMPOUND_STRING("Leftovers"),
    [HOLD_EFFECT_DRAGON_SCALE]     = COMPOUND_STRING("Dragon Scale"),
    [HOLD_EFFECT_LIGHT_BALL]       = COMPOUND_STRING("Light Ball"),
    [HOLD_EFFECT_TYPE_POWER]       = COMPOUND_STRING("Type Power"),
    [HOLD_EFFECT_UPGRADE]          = COMPOUND_STRING("Upgrade"),
    [HOLD_EFFECT_SHELL_BELL]       = COMPOUND_STRING("Shell Bell"),
    [HOLD_EFFECT_LUCKY_PUNCH]      = COMPOUND_STRING("Lucky Punch"),
    [HOLD_EFFECT_METAL_POWDER]     = COMPOUND_STRING("Metal Powder"),
    [HOLD_EFFECT_THICK_CLUB]       = COMPOUND_STRING("Thick Club"),
    [HOLD_EFFECT_LEEK]             = COMPOUND_STRING("Leek"),
    [HOLD_EFFECT_CHOICE_SCARF]     = COMPOUND_STRING("Choice Scarf"),
    [HOLD_EFFECT_CHOICE_SPECS]     = COMPOUND_STRING("Choice Specs"),
    [HOLD_EFFECT_DAMP_ROCK]        = COMPOUND_STRING("Damp Rock"),
    [HOLD_EFFECT_GRIP_CLAW]        = COMPOUND_STRING("Grip Claw"),
    [HOLD_EFFECT_HEAT_ROCK]        = COMPOUND_STRING("Heat Rock"),
    [HOLD_EFFECT_ICY_ROCK]         = COMPOUND_STRING("Icy Rock"),
    [HOLD_EFFECT_LIGHT_CLAY]       = COMPOUND_STRING("Light Clay"),
    [HOLD_EFFECT_SMOOTH_ROCK]      = COMPOUND_STRING("Smooth Rock"),
    [HOLD_EFFECT_POWER_HERB]       = COMPOUND_STRING("Power Herb"),
    [HOLD_EFFECT_BIG_ROOT]         = COMPOUND_STRING("Big Root"),
    [HOLD_EFFECT_EXPERT_BELT]      = COMPOUND_STRING("Expert Belt"),
    [HOLD_EFFECT_LIFE_ORB]         = COMPOUND_STRING("Life Orb"),
    [HOLD_EFFECT_METRONOME]        = COMPOUND_STRING("Metronome"),
    [HOLD_EFFECT_MUSCLE_BAND]      = COMPOUND_STRING("Muscle Band"),
    [HOLD_EFFECT_WIDE_LENS]        = COMPOUND_STRING("Wide Lens"),
    [HOLD_EFFECT_WISE_GLASSES]     = COMPOUND_STRING("Wise Glasses"),
    [HOLD_EFFECT_ZOOM_LENS]        = COMPOUND_STRING("Zoom Lens"),
    [HOLD_EFFECT_LAGGING_TAIL]     = COMPOUND_STRING("Lagging Tail"),
    [HOLD_EFFECT_FOCUS_SASH]       = COMPOUND_STRING("Focus Sash"),
    [HOLD_EFFECT_FLAME_ORB]        = COMPOUND_STRING("Flame Orb"),
    [HOLD_EFFECT_TOXIC_ORB]        = COMPOUND_STRING("Toxic Orb"),
    [HOLD_EFFECT_STICKY_BARB]      = COMPOUND_STRING("Sticky Barb"),
    [HOLD_EFFECT_IRON_BALL]        = COMPOUND_STRING("Iron Ball"),
    [HOLD_EFFECT_BLACK_SLUDGE]     = COMPOUND_STRING("Black Sludge"),
    [HOLD_EFFECT_DESTINY_KNOT]     = COMPOUND_STRING("Destiny Knot"),
    [HOLD_EFFECT_SHED_SHELL]       = COMPOUND_STRING("Shed Shell"),
    [HOLD_EFFECT_QUICK_POWDER]     = COMPOUND_STRING("Quick Powder"),
    [HOLD_EFFECT_ADAMANT_ORB]      = COMPOUND_STRING("Adamant Orb"),
    [HOLD_EFFECT_LUSTROUS_ORB]     = COMPOUND_STRING("Lustrous Orb"),
    [HOLD_EFFECT_GRISEOUS_ORB]     = COMPOUND_STRING("Griseous Orb"),
    [HOLD_EFFECT_ENIGMA_BERRY]     = COMPOUND_STRING("Enigma Berry"),
    [HOLD_EFFECT_RESIST_BERRY]     = COMPOUND_STRING("Resist Berry"),
    [HOLD_EFFECT_POWER_ITEM]       = COMPOUND_STRING("Power Item"),
    [HOLD_EFFECT_RESTORE_PCT_HP]   = COMPOUND_STRING("Restore Pct Hp"),
    [HOLD_EFFECT_MICLE_BERRY]      = COMPOUND_STRING("Micle Berry"),
    [HOLD_EFFECT_CUSTAP_BERRY]     = COMPOUND_STRING("Custap Berry"),
    [HOLD_EFFECT_JABOCA_BERRY]     = COMPOUND_STRING("Jaboca Berry"),
    [HOLD_EFFECT_ROWAP_BERRY]      = COMPOUND_STRING("Rowap Berry"),
    [HOLD_EFFECT_KEE_BERRY]        = COMPOUND_STRING("Kee Berry"),
    [HOLD_EFFECT_MARANGA_BERRY]    = COMPOUND_STRING("Maranga Berry"),
    [HOLD_EFFECT_PLATE]            = COMPOUND_STRING("Plate"),
    [HOLD_EFFECT_FLOAT_STONE]      = COMPOUND_STRING("Float Stone"),
    [HOLD_EFFECT_EVIOLITE]         = COMPOUND_STRING("Eviolite"),
    [HOLD_EFFECT_ASSAULT_VEST]     = COMPOUND_STRING("Assault Vest"),
    [HOLD_EFFECT_DRIVE]            = COMPOUND_STRING("Drive"),
    [HOLD_EFFECT_GEMS]             = COMPOUND_STRING("Gems"),
    [HOLD_EFFECT_ROCKY_HELMET]     = COMPOUND_STRING("Rocky Helmet"),
    [HOLD_EFFECT_AIR_BALLOON]      = COMPOUND_STRING("Air Balloon"),
    [HOLD_EFFECT_RED_CARD]         = COMPOUND_STRING("Red Card"),
    [HOLD_EFFECT_RING_TARGET]      = COMPOUND_STRING("Ring Target"),
    [HOLD_EFFECT_BINDING_BAND]     = COMPOUND_STRING("Binding Band"),
    [HOLD_EFFECT_EJECT_BUTTON]     = COMPOUND_STRING("Eject Button"),
    [HOLD_EFFECT_ABSORB_BULB]      = COMPOUND_STRING("Absorb Bulb"),
    [HOLD_EFFECT_CELL_BATTERY]     = COMPOUND_STRING("Cell Battery"),
    [HOLD_EFFECT_MEGA_STONE]       = COMPOUND_STRING("Mega Stone"),
    [HOLD_EFFECT_SAFETY_GOGGLES]   = COMPOUND_STRING("Safety Goggles"),
    [HOLD_EFFECT_LUMINOUS_MOSS]    = COMPOUND_STRING("Luminous Moss"),
    [HOLD_EFFECT_SNOWBALL]         = COMPOUND_STRING("Snowball"),
    [HOLD_EFFECT_WEAKNESS_POLICY]  = COMPOUND_STRING("Weakness Policy"),
    [HOLD_EFFECT_PRIMAL_ORB]       = COMPOUND_STRING("Primal Orb"),
    [HOLD_EFFECT_PROTECTIVE_PADS]  = COMPOUND_STRING("Protective Pads"),
    [HOLD_EFFECT_TERRAIN_EXTENDER] = COMPOUND_STRING("Terrain Extender"),
    [HOLD_EFFECT_SEEDS]            = COMPOUND_STRING("Seeds"),
    [HOLD_EFFECT_ADRENALINE_ORB]   = COMPOUND_STRING("Adrenaline Orb"),
    [HOLD_EFFECT_MEMORY]           = COMPOUND_STRING("Memory"),
    [HOLD_EFFECT_Z_CRYSTAL]        = COMPOUND_STRING("Z-Crystal"),
    [HOLD_EFFECT_UTILITY_UMBRELLA] = COMPOUND_STRING("Utility Umbrella"),
    [HOLD_EFFECT_EJECT_PACK]       = COMPOUND_STRING("Eject Pack"),
    [HOLD_EFFECT_ROOM_SERVICE]     = COMPOUND_STRING("Room Service"),
    [HOLD_EFFECT_BLUNDER_POLICY]   = COMPOUND_STRING("Blunder Policy"),
    [HOLD_EFFECT_HEAVY_DUTY_BOOTS] = COMPOUND_STRING("Heavy Duty Boots"),
    [HOLD_EFFECT_THROAT_SPRAY]     = COMPOUND_STRING("Throat Spray"),
    [HOLD_EFFECT_ABILITY_SHIELD]   = COMPOUND_STRING("Ability Shield"),
    [HOLD_EFFECT_CLEAR_AMULET]     = COMPOUND_STRING("Clear Amulet"),
    [HOLD_EFFECT_MIRROR_HERB]      = COMPOUND_STRING("Mirror Herb"),
    [HOLD_EFFECT_PUNCHING_GLOVE]   = COMPOUND_STRING("Punching Glove"),
    [HOLD_EFFECT_COVERT_CLOAK]     = COMPOUND_STRING("Covert Cloak"),
    [HOLD_EFFECT_LOADED_DICE]      = COMPOUND_STRING("Loaded Dice"),
    [HOLD_EFFECT_BOOSTER_ENERGY]   = COMPOUND_STRING("Booster Energy"),
    [HOLD_EFFECT_OGERPON_MASK]     = COMPOUND_STRING("Ogerpon Mask"),
    [HOLD_EFFECT_BERSERK_GENE]     = COMPOUND_STRING("Berserk Gene"),
};
static const u8 *GetHoldEffectName(enum ItemHoldEffect holdEffect)
{
    if (sHoldEffectNames[holdEffect] == NULL)
        return sHoldEffectNames[0];
    return sHoldEffectNames[holdEffect];
}
